异步编程是 JavaScript 的核心难点之一,而 Promise 作为现代异步编程的基石,深刻影响着 async/await、fetch 等 API 的设计。理解 Promise 的最好方式,就是亲手实现一个符合 Promises/A+ 规范的库。本文将带你从零开始,逐步构建一个可用的 Promise 实现。
为什么需要 Promises/A+ 规范
在开始编码之前,我们需要明确目标。Promises/A+ 规范定义了 then 方法的行为标准,包括:
- Promise 的三种状态:pending、fulfilled、rejected
- 状态一旦改变就不可逆转
then方法必须返回一个新的 Promise- 回调必须异步执行
- 值穿透与错误捕获机制
遵循这套规范意味着我们的实现可以与其他 Promise 库互操作,也能通过官方测试套件的验证。
基础骨架:状态管理与构造函数
首先定义 Promise 的三种状态和基本结构:
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class MyPromise {
constructor(executor) {
this.state = PENDING;
this.value = undefined;
this.reason = undefined;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.state !== PENDING) return;
this.state = FULFILLED;
this.value = value;
this.onFulfilledCallbacks.forEach(fn => fn());
};
const reject = (reason) => {
if (this.state !== PENDING) return;
this.state = REJECTED;
this.reason = reason;
this.onRejectedCallbacks.forEach(fn => fn());
};
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
}
这里用两个回调数组保存 pending 状态下的订阅者,当状态改变时统一触发。try/catch 确保执行器内部抛出的异常能被正确捕获。
实现 then 方法:核心难点
then 方法是整个规范的核心,需要处理同步值、异步回调、链式调用和 thenable 对象等多种情况:
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };
const promise2 = new MyPromise((resolve, reject) => {
const handleFulfilled = () => {
queueMicrotask(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (err) {
reject(err);
}
});
};
const handleRejected = () => {
queueMicrotask(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (err) {
reject(err);
}
});
};
if (this.state === FULFILLED) {
handleFulfilled();
} else if (this.state === REJECTED) {
handleRejected();
} else {
this.onFulfilledCallbacks.push(handleFulfilled);
this.onRejectedCallbacks.push(handleRejected);
}
});
return promise2;
}
几个关键设计点:
- 值穿透:当
onFulfilled不是函数时,默认传递值;当onRejected不是函数时,默认抛出错误。 - 异步执行:使用
queueMicrotask保证回调在微任务中执行,符合规范要求。 - 返回新 Promise:每次调用
then都返回新的 Promise,支持链式调用。
resolvePromise:处理返回值
这是最复杂的部分,需要判断返回值 x 的各种情况:
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected'));
}
if (x instanceof MyPromise) {
x.then(resolve, reject);
return;
}
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
let called = false;
try {
const then = x.then;
if (typeof then === 'function') {
then.call(
x,
y => {
if (called) return;
called = true;
resolvePromise(promise2, y, resolve, reject);
},
r => {
if (called) return;
called = true;
reject(r);
}
);
} else {
resolve(x);
}
} catch (err) {
if (called) return;
called = true;
reject(err);
}
} else {
resolve(x);
}
}
这段代码处理了三种情况:循环引用检测、Promise 实例的直接传递、以及 thenable 对象的递归解析。called 标志位确保 resolve/reject 只被调用一次。
静态方法与原型方法
为了让实现更完整,我们补充常用的静态方法:
static resolve(value) {
if (value instanceof MyPromise) return value;
return new MyPromise(resolve => resolve(value));
}
static reject(reason) {
return new MyPromise((_, reject) => reject(reason));
}
static all(promises) {
return new MyPromise((resolve, reject) => {
const results = [];
let count = 0;
if (promises.length === 0) return resolve(results);
promises.forEach((p, i) => {
MyPromise.resolve(p).then(value => {
results[i] = value;
if (++count === promises.length) resolve(results);
}, reject);
});
});
}
以及实例方法 catch 和 finally:
catch(onRejected) {
return this.then(null, onRejected);
}
finally(callback) {
return this.then(
value => MyPromise.resolve(callback()).then(() => value),
reason => MyPromise.resolve(callback()).then(() => { throw reason; })
);
}
验证与测试
完成实现后,可以用 promises-aplus-tests 套件验证:
npm install promises-aplus-tests --save-dev
配置适配器后运行测试,全部通过说明实现符合规范。常见的坑包括:回调未异步执行、循环引用未检测、thenable 的 then 方法被调用多次等。
总结
手写 Promise 的过程让我们深入理解了异步编程的底层机制。核心要点可以归纳为:
- 状态机:pending → fulfilled/rejected 的单向转换
- 发布订阅:pending 状态下收集回调,状态改变后统一触发
- 微任务调度:保证回调异步执行,避免阻塞主线程
- 递归解析:通过 resolvePromise 处理各种返回值类型
掌握这些原理后,阅读 async/await 的编译产物或调试异步代码都会更加得心应手。建议读者在理解的基础上,尝试自己动手实现一遍,并跑通官方测试套件,这比单纯阅读代码的收获要大得多。
未经允许不得转载:任鹏个人博客 » 手写 Promise 全流程:从零实现符合 Promises/A+ 规范的库

