一、Promise 基础回顾

在开始深入探讨 Promise 的 then 链式调用等特性之前,我们先来简单回顾一下 Promise 的基本概念。Promise 是一种用于处理异步操作的对象,它有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。

例如,我们可以用 JavaScript 创建一个简单的 Promise:

// 创建一个Promise,模拟一个异步操作,比如延迟1秒后返回成功结果
const myPromise = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('操作成功');
    }, 1000);
});

在这个例子中,我们通过 setTimeout 模拟了一个异步操作,1 秒后调用 resolve 方法,将 Promise 的状态从 pending 变为 fulfilled

二、then 链式调用的递归解析

2.1 基本的 then 链式调用

then 方法是 Promise 中非常重要的一个方法,它用于处理 Promise 成功或失败的情况。当一个 Promise 被解决(resolved)时,会执行 then 方法中的第一个回调函数;当 Promise 被拒绝(rejected)时,会执行 then 方法中的第二个回调函数(可选)。

下面是一个简单的 then 链式调用示例:

myPromise
   .then((result) => {
        console.log(result); // 输出 '操作成功'
        return result.toUpperCase();
    })
   .then((newResult) => {
        console.log(newResult); // 输出 '操作成功' 转换后的大写形式
    })
   .catch((error) => {
        console.error(error);
    });

在这个示例中,第一个 then 方法中的回调函数接收 resolve 传递的结果,并将其转换为大写后返回。第二个 then 方法则接收第一个 then 方法返回的结果并进行处理。

2.2 递归解析过程

那么,then 链式调用是如何进行递归解析的呢?当我们调用一个 Promise 的 then 方法时,它会返回一个新的 Promise。这个新的 Promise 的状态会根据当前 then 方法中回调函数的返回值来确定。

如果回调函数返回一个非 Promise 值,那么新的 Promise 会立即被解决(resolved),其值为回调函数的返回值。如果回调函数返回一个 Promise,那么新的 Promise 的状态会跟随这个返回的 Promise 的状态。

例如:

const promise1 = new Promise((resolve) => {
    resolve(1);
});

const promise2 = promise1.then((result) => {
    console.log(result); // 输出 1
    return new Promise((resolveInner) => {
        setTimeout(() => {
            resolveInner(result * 2);
        }, 1000);
    });
});

promise2.then((newResult) => {
    console.log(newResult); // 输出 2(延迟1秒后)
});

在这个例子中,promise1 解决后,promise2 的 then 方法中的回调函数返回了一个新的 Promise。这个新的 Promise 会在 1 秒后解决,并且 promise2 的状态会跟随这个新 Promise 的状态。

三、resolvePromise 过程

3.1 resolvePromise 函数的作用

resolvePromise 函数在 Promise 的实现中起着关键作用,它用于处理 Promise 的解决过程。当一个 Promise 被解决时,会调用 resolvePromise 函数来确定其最终状态。

3.2 resolvePromise 过程示例

我们来看一个简单的示例,假设我们有一个自定义的 Promise 实现(简化版):

function MyPromise(executor) {
    this.state = 'pending';
    this.value = null;
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];

    const resolve = (value) => {
        if (this.state === 'pending') {
            this.state = 'fulfilled';
            this.value = value;
            this.onFulfilledCallbacks.forEach((callback) => callback(this.value));
        }
    };

    const reject = (reason) => {
        if (this.state === 'pending') {
            this.state ='rejected';
            this.value = reason;
            this.onRejectedCallbacks.forEach((callback) => callback(this.value));
        }
    };

    try {
        executor(resolve, reject);
    } catch (error) {
        reject(error);
    }
}

MyPromise.prototype.then = function (onFulfilled, onRejected) {
    const newPromise = new MyPromise((resolveNew, rejectNew) => {
        const fulfilledCb = (value) => {
            try {
                const result = onFulfilled(value);
                resolvePromise(newPromise, result, resolveNew, rejectNew);
            } catch (error) {
                rejectNew(error);
            }
        };

        const rejectedCb = (reason) => {
            try {
                const result = onRejected(reason);
                resolvePromise(newPromise, result, resolveNew, rejectNew);
            } catch (error) {
                rejectNew(error);
            }
        };

        if (this.state === 'fulfilled') {
            fulfilledCb(this.value);
        } else if (this.state ==='rejected') {
            rejectedCb(this.value);
        } else {
            this.onFulfilledCallbacks.push(fulfilledCb);
            this.onRejectedCallbacks.push(rejectedCb);
        }
    });

    return newPromise;
};

function resolvePromise(promise, x, resolve, reject) {
    if (promise === x) {
        return reject(new Error('循环引用'));
    }

    if (x instanceof MyPromise) {
        if (x.state === 'pending') {
            x.then((value) => {
                resolvePromise(promise, value, resolve, reject);
            }, reject);
        } else {
            x.then(resolve, reject);
        }
    } else {
        resolve(x);
    }
}

// 使用我们自定义的Promise
const myCustomPromise = new MyPromise((resolve) => {
    resolve(10);
});

myCustomPromise
   .then((result) => {
        console.log(result); // 输出 10
        return result * 2;
    })
   .then((newResult) => {
        console.log(newResult); // 输出 20
    });

在这个自定义的 Promise 实现中,resolvePromise 函数首先检查 promisex 是否为同一个对象,如果是则抛出循环引用错误。然后检查 x 是否是一个 MyPromise 对象,如果是且状态为 pending,则递归调用 resolvePromise 处理其解决结果;如果 x 不是 MyPromise 对象,则直接解决 promise

四、异步调度细节

4.1 宏任务与微任务

在 JavaScript 中,异步操作的执行涉及到宏任务和微任务。宏任务包括 setTimeoutsetIntervalrequestAnimationFrame 等,微任务包括 Promisethen 回调、MutationObserver 等。

例如:

console.log('开始');

setTimeout(() => {
    console.log('setTimeout 宏任务');
}, 0);

Promise.resolve()
   .then(() => {
        console.log('Promise then 微任务');
    });

console.log('结束');

在这个例子中,首先输出 开始,然后是 结束,接着是 Promise then 微任务,最后是 setTimeout 宏任务。这是因为微任务会在当前调用栈清空后立即执行,而宏任务会在微任务队列清空后执行。

4.2 Promise 异步调度原理

Promise 的 then 方法中的回调函数是通过微任务来调度的。当一个 Promise 被解决或拒绝时,它的 then 回调函数会被放入微任务队列中,等待当前调用栈清空后执行。

例如:

const promise = new Promise((resolve) => {
    resolve('异步操作完成');
});

console.log('主线程代码');

promise.then((result) => {
    console.log(result); // 输出 '异步操作完成'
});

console.log('更多主线程代码');

在这个例子中,console.log('主线程代码')console.log('更多主线程代码') 会先执行,然后 Promise 的 then 回调函数会被放入微任务队列,在主线程代码执行完毕后,微任务队列中的 Promise 回调函数才会执行。

五、应用场景

5.1 处理异步操作序列

Promise 的 then 链式调用非常适合处理一系列的异步操作,例如在一个网络请求完成后,根据响应结果进行后续的操作,如数据处理、再次请求等。

例如:

// 模拟网络请求
function makeRequest(url) {
    return new Promise((resolve) => {
        setTimeout(() => {
            const response = { data: '请求成功' };
            resolve(response);
        }, 1000);
    });
}

makeRequest('https://example.com/api')
   .then((response) => {
        console.log(response.data); // 输出 '请求成功'
        return makeRequest('https://example.com/api/next');
    })
   .then((nextResponse) => {
        console.log(nextResponse.data); // 输出下一个请求的结果
    });

5.2 错误处理

Promise 的 catch 方法可以统一处理整个链式调用中的错误,使得错误处理更加集中和方便。

例如:

makeRequest('https://example.com/api')
   .then((response) => {
        console.log(response.data);
        throw new Error('模拟错误'); // 抛出错误
    })
   .then((nextResponse) => {
        console.log(nextResponse.data); // 不会执行到这里
    })
   .catch((error) => {
        console.error(error.message); // 输出 '模拟错误'
    });

六、技术优缺点

6.1 优点

  • 代码清晰:通过 then 链式调用,异步操作的流程更加清晰,避免了回调地狱。
  • 错误处理方便catch 方法可以统一处理整个链式调用中的错误。
  • 支持并发操作:可以通过 Promise.all 等方法处理并发的异步操作。

6.2 缺点

  • 学习成本:对于初学者来说,Promise 的概念和特性可能需要一定的时间来理解和掌握。
  • 调试困难:由于异步操作的特性,调试 Promise 相关的代码可能会比较困难。

七、注意事项

7.1 避免循环引用

在使用 Promise 时,要注意避免循环引用,否则会导致 resolvePromise 函数抛出错误。

7.2 合理使用微任务和宏任务

要清楚地了解微任务和宏任务的执行顺序,避免出现意外的结果。

7.3 处理错误

在 Promise 链式调用中,要确保每个 then 方法都正确处理错误,或者在最后使用 catch 方法统一处理。

八、文章总结

本文深入探讨了 Promise 的 then 链式调用的递归解析、resolvePromise 过程与异步调度细节。通过详细的示例和解释,我们了解了 Promise 的基本概念、then 链式调用的工作原理、resolvePromise 函数的作用以及异步调度的机制。同时,我们还介绍了 Promise 的应用场景、优缺点和注意事项。希望读者通过本文能够对 Promise 有更深入的理解和掌握,在实际开发中更好地使用 Promise 处理异步操作。