一、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 函数首先检查 promise 和 x 是否为同一个对象,如果是则抛出循环引用错误。然后检查 x 是否是一个 MyPromise 对象,如果是且状态为 pending,则递归调用 resolvePromise 处理其解决结果;如果 x 不是 MyPromise 对象,则直接解决 promise。
四、异步调度细节
4.1 宏任务与微任务
在 JavaScript 中,异步操作的执行涉及到宏任务和微任务。宏任务包括 setTimeout、setInterval、requestAnimationFrame 等,微任务包括 Promise 的 then 回调、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 处理异步操作。
评论
围绕“手写Promise从零到全特性:理解then链式调用的递归解析、resolvePromise过程与异步调度细节”参与讨论