1. 为什么我们需要重新认识Promise?
十年前我刚接触JavaScript时,回调地狱(callback hell)是每个前端开发者都要面对的噩梦。金字塔式的嵌套回调不仅难以阅读,错误处理更是令人崩溃。直到ES6引入Promise,才让我们有了摆脱这种困境的武器。
但Promise真的被用好了吗?我在代码审查中经常看到这样的反模式:
javascript复制// 典型的Promise误用
new Promise((resolve) => {
fetch('/api/data').then(resolve)
}).then(handleData)
这种"Promise构造函数反模式"恰恰说明很多人对Promise的理解还停留在表面。本文将带你从Event Loop的底层机制出发,彻底掌握Promise的正确打开方式。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Promise核心机制解析
2.1 状态机:Promise的三种状态
Promise本质上是一个状态机,包含三种状态:
- Pending(等待中)
- Fulfilled(已成功)
- Rejected(已失败)
状态转换是不可逆的,这保证了Promise的稳定性。但很多人忽略了一个关键点:then方法返回的是一个新的Promise。这是链式调用的基础,也是许多陷阱的根源。
javascript复制const p1 = Promise.resolve(1);
const p2 = p1.then(v => v + 1);
console.log(p1 === p2); // false
2.2 Microtask队列与事件循环
Promise的回调会被放入Microtask队列,这使其优先级高于常规的Macrotask(如setTimeout)。理解这一点对调试异步代码至关重要:
javascript复制console.log('script start');
setTimeout(() => {
console.log('setTimeout');
}, 0);
Promise.resolve().then(() => {
console.log('promise1');
}).then(() => {
console.log('promise2');
});
console.log('script end');
// 输出顺序:
// script start
// script end
// promise1
// promise2
// setTimeout
重要提示:Microtask会在当前Macrotask执行完后立即执行,期间会清空整个Microtask队列。
3. 高级Promise模式
3.1 取消Promise的几种方案
原生Promise不支持取消操作,这在需要中断异步请求时很不方便。以下是几种实现方案:
- AbortController方案(推荐):
javascript复制const controller = new AbortController();
const signal = controller.signal;
fetch('/api', { signal })
.then(handleResponse)
.catch(err => {
if (err.name === 'AbortError') {
console.log('请求已取消');
}
});
// 取消请求
controller.abort();
- 封装可取消Promise:
javascript复制function makeCancelable(promise) {
let hasCanceled = false;
const wrappedPromise = new Promise((resolve, reject) => {
promise.then(
val => hasCanceled ? reject({isCanceled: true}) : resolve(val),
error => hasCanceled ? reject({isCanceled: true}) : reject(error)
);
});
return {
