Promise 实现原理:手写一个符合 Promises/A+ 规范的 Promise
Promise 实现原理:手写一个符合 Promises/A+ 规范的 Promise
目标:从 0 实现一个最小可用且符合 Promises/A+ 规范的
Promise
,并逐步完善到支持then
链式调用、错误穿透、catch
/finally
、以及常用静态方法。
目录
- Promise 实现原理:手写一个符合 Promises/A+ 规范的 Promise
- 阅读指引与学习路线图(先用后写,循序渐进)
- 先会用再会写:Promise 入门示例
- 1. 为什么需要 Promise(痛点与演进)
- 2. Promises/A+ 规范核心(简化版)
- 3. 最小可用版 MyPromise:状态机与 then
- 3.1 逐行实现(含详细注释)
- 4. Promise 解析过程(Promise Resolution Procedure)
- 5. 异步调度:微任务 vs 宏任务
- 6. 完善 API:catch、finally、静态方法
- 7. 常见陷阱与测试用例
- 8. 总结与附录
阅读指引与学习路线图(先用后写,循序渐进)
建议路径:
- 先从“入门示例”理解 Promise 的使用体验;
- 再看“为什么需要”和“A+ 规范核心”,掌握规则;
- 然后手写“最小可用版”,配合“逐行注释”快速吃透;
- 最后完善 API、理解解析过程与异步调度,查看总流程图串联全局。
先会用再会写:Promise 入门示例
目标:顺序执行“查用户 → 查订单 → 查支付”,并统一错误处理。
// 模拟三个异步函数(返回原生 Promise)
const fetchUser = (id) => Promise.resolve({ id, name: 'Ada' });
const fetchOrders = (userId) => Promise.resolve([{ id: 1, userId }]);
const fetchPayment = (orderId) => Promise.resolve({ orderId, status: 'paid' });fetchUser(42).then((user) => fetchOrders(user.id)) // then 链式:上一步结果传给下一步.then((orders) => fetchPayment(orders[0].id)).then((payment) => {console.log('流水线成功:', payment);}).catch((err) => { // 任何一步失败都会到这里console.error('有环节失败:', err);}).finally(() => { // 无论成功或失败都会执行console.log('流程结束');});
想一想:为什么 then
会返回一个“新的” Promise?为什么回调是异步触发?下面我们会从规范和实现两头验证你的直觉。
1. 为什么需要 Promise(痛点与演进)
- 解决回调地狱:回调嵌套难以阅读、错误处理分散。
- 语义更清晰:状态机(pending → fulfilled/rejected),一次决议不可逆。
- 可组合:链式调用、并发聚合(
all
、race
等)。
2. Promises/A+ 规范核心(简化版)
- 三态:pending → fulfilled | rejected;状态不可逆。
then(onFulfilled, onRejected)
:- 两个回调都是可选;
- 必须异步执行(规范:“must be called after the current run-to-completion”);
then
返回一个新 Promise:其命运由回调返回值x
决定(“解析过程”)。
- 解析过程(
[[Resolve]](promise, x)
):- 如果
x
是当前 promise,则拒绝(TypeError,环引用保护)。 - 如果
x
是对象/函数,且有then
,以x.then
的结果来决定; - 否则以
x
成功。
- 如果
3. 最小可用版 MyPromise:状态机与 then
实现要点:
- 内部保存状态、值/原因、回调队列。
- 构造器接收执行器
(resolve, reject)
,同步立即调用。 then
:订阅回调,状态已定则异步派发;返回新MyPromise
。
const isFunction = fn => typeof fn === 'function';
const asyncQueue = fn => (typeof queueMicrotask === 'function' ? queueMicrotask(fn) : setTimeout(fn, 0));const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';class MyPromise {constructor(executor) {this._state = PENDING;this._value = undefined;this._fulfilledHandlers = [];this._rejectedHandlers = [];const resolve = value => {this._transition(FULFILLED, value);};const reject = reason => {this._transition(REJECTED, reason);};try {executor(resolve, reject);} catch (err) {reject(err);}}_transition(nextState, value) {if (this._state !== PENDING) return;if (nextState === FULFILLED && value === this) {return this._transition(REJECTED, new TypeError('Chaining cycle detected for promise'));}this._state = nextState;this._value = value;const queue = nextState === FULFILLED ? this._fulfilledHandlers : this._rejectedHandlers;asyncQueue(() => {queue.forEach(handler => handler(value));this._fulfilledHandlers = [];this._rejectedHandlers = [];});}then(onFulfilled, onRejected) {const realOnFulfilled = isFunction(onFulfilled) ? onFulfilled : v => v;const realOnRejected = isFunction(onRejected) ? onRejected : e => { throw e; };const parent = this;return new MyPromise((resolve, reject) => {const wrap = (cb, val, resolve, reject) => {try {const x = cb(val);MyPromise._resolvePromise(resolve, reject, x);} catch (err) {reject(err);}};const handleFulfilled = value => wrap(realOnFulfilled, value, resolve, reject);const handleRejected = reason => wrap(realOnRejected, reason, resolve, reject);if (parent._state === PENDING) {parent._fulfilledHandlers.push(handleFulfilled);parent._rejectedHandlers.push(handleRejected);} else if (parent._state === FULFILLED) {asyncQueue(() => handleFulfilled(parent._value));} else {asyncQueue(() => handleRejected(parent._value));}});}// Promises/A+ 2.3 解析过程static _resolvePromise(resolve, reject, x) {if (x === undefined || x === null) return resolve(x);if (x === MyPromise) return reject(new TypeError('Invalid resolution'));if (x && (typeof x === 'object' || typeof x === 'function')) {let then;try { then = x.then; } catch (err) { return reject(err); }if (typeof then === 'function') {let called = false;try {then.call(x,y => { if (called) return; called = true; MyPromise._resolvePromise(resolve, reject, y); },r => { if (called) return; called = true; reject(r); });} catch (err) {if (!called) reject(err);}return;}}resolve(x);}
}
3.1 逐行实现(含详细注释)
下面这份代码与上面的实现等价,但我把每个关键步骤都写上了中文注释,读完你就能“看懂即会写”。
// 工具函数:判断是否为函数
const isFunction = (fn) => typeof fn === 'function';// 工具函数:异步派发。优先使用微任务(更贴近原生 Promise 的时序),降级到 setTimeout
const asyncQueue = (task) => (typeof queueMicrotask === 'function' ? queueMicrotask(task) : setTimeout(task, 0));// 三种状态常量(不可逆)
const PENDING = 'pending'; // 初始状态,既不成功也不失败
const FULFILLED = 'fulfilled'; // 已成功
const REJECTED = 'rejected'; // 已失败class MyPromise {// 构造器接受一个执行器函数,立即同步执行constructor(executor) {// 内部状态与数据this._state = PENDING; // 当前状态this._value = undefined; // 成功值或失败原因this._fulfilledHandlers = []; // 成功回调队列this._rejectedHandlers = []; // 失败回调队列// 将外部可见的 resolve/reject 封装为闭包,限制状态切换入口const resolve = (value) => {this._transition(FULFILLED, value);};const reject = (reason) => {this._transition(REJECTED, reason);};// 规范:构造阶段同步调用 executor,并捕获其中同步异常 → 走 rejecttry {executor(resolve, reject);} catch (error) {reject(error);}}// 内部状态机:只允许从 pending → fulfilled/rejected,且只发生一次_transition(nextState, value) {if (this._state !== PENDING) return; // 已经决议过,忽略后续调用// 防御:如果以自身作为值解决,会造成链式循环,需转为拒绝if (nextState === FULFILLED && value === this) {return this._transition(REJECTED, new TypeError('Chaining cycle detected for promise'));}// 状态与数据落定this._state = nextState;this._value = value;// 根据状态选择应该派发的回调队列const queue = nextState === FULFILLED ? this._fulfilledHandlers : this._rejectedHandlers;// 规范:then 注册的回调必须异步执行(下一轮事件循环/微任务)asyncQueue(() => {queue.forEach((handler) => handler(value));// 回调只消费一次,派发后清空引用,帮助 GCthis._fulfilledHandlers = [];this._rejectedHandlers = [];});}// then 返回一个新的 Promise;回调的返回值 x 决定新 Promise 的命运then(onFulfilled, onRejected) {// 非函数需要“透传/抛出”,以符合规范的值穿透/错穿透行为const realOnFulfilled = isFunction(onFulfilled) ? onFulfilled : (v) => v;const realOnRejected = isFunction(onRejected) ? onRejected : (e) => { throw e; };const parent = this;return new MyPromise((resolve, reject) => {// 包装器:执行回调并进入解析过程const run = (callback, input) => {try {const x = callback(input); // 回调返回值 xMyPromise._resolvePromise(resolve, reject, x); // 按规范解析 x} catch (err) {reject(err); // 回调抛错 → 拒绝}};const handleFulfilled = (value) => run(realOnFulfilled, value);const handleRejected = (reason) => run(realOnRejected, reason);// 三种时机:if (parent._state === PENDING) {// 仍在挂起:把处理器入队,等待未来异步派发parent._fulfilledHandlers.push(handleFulfilled);parent._rejectedHandlers.push(handleRejected);} else if (parent._state === FULFILLED) {// 已成功:异步执行成功处理器asyncQueue(() => handleFulfilled(parent._value));} else {// 已失败:异步执行失败处理器asyncQueue(() => handleRejected(parent._value));}});}// 解析过程:[[Resolve]](promise, x)// 目标:决定 then 返回的新 promise 的状态static _resolvePromise(resolve, reject, x) {// 1) 普通值/undefined/null 直接成功if (x === undefined || x === null) return resolve(x);// 防御:不允许把构造函数本身当作值解析if (x === MyPromise) return reject(new TypeError('Invalid resolution'));// 2) 如果 x 是对象或函数,可能是 thenableif (x && (typeof x === 'object' || typeof x === 'function')) {let then;try {then = x.then; // 取 then 属性时也可能抛错} catch (err) {return reject(err);}// 2.1) 如果 then 是函数,按 thenable 处理if (typeof then === 'function') {let called = false; // 防止多次调用 resolve/rejecttry {then.call(x,(y) => { if (called) return; called = true; MyPromise._resolvePromise(resolve, reject, y); },(r) => { if (called) return; called = true; reject(r); });} catch (err) {if (!called) reject(err); // 若回调未被调用,异常即为拒绝原因}return;}}// 3) 非 thenable 值,直接成功resolve(x);}
}
4. Promise 解析过程(Promise Resolution Procedure)
- 递归地“拆箱”
thenable
; - 防御重复调用(
called
标志); - 检测环(链式返回自身 → TypeError)。
示例:
const p = new MyPromise(r => r(42)).then(v => ({ then(res) { res(v + 1); } })) // thenable.then(v => v * 2);p.then(console.log); // 86
5. 异步调度:微任务 vs 宏任务
- 规范要求
then
回调异步执行; - 浏览器/Node 的原生 Promise 使用微任务(
queueMicrotask
/process.nextTick
); - 我们实现中优先
queueMicrotask
,降级setTimeout 0
。
6. 完善 API:catch、finally、静态方法
MyPromise.prototype.catch = function(onRejected) {return this.then(undefined, onRejected);
};MyPromise.prototype.finally = function(onFinally) {const handler = isFunction(onFinally) ? onFinally : () => {};return this.then(value => MyPromise.resolve(handler()).then(() => value),reason => MyPromise.resolve(handler()).then(() => { throw reason; }));
};MyPromise.resolve = function(value) {if (value instanceof MyPromise) return value;return new MyPromise(resolve => resolve(value));
};MyPromise.reject = function(reason) {return new MyPromise((_, reject) => reject(reason));
};MyPromise.all = function(iterable) {return new MyPromise((resolve, reject) => {const results = [];let remaining = 0, index = 0;for (const item of iterable) {const currentIndex = index++;remaining++;MyPromise.resolve(item).then(val => {results[currentIndex] = val;if (--remaining === 0) resolve(results);},reject);}if (index === 0) resolve([]);});
};MyPromise.race = function(iterable) {return new MyPromise((resolve, reject) => {for (const item of iterable) {MyPromise.resolve(item).then(resolve, reject);}});
};MyPromise.allSettled = function(iterable) {return new MyPromise(resolve => {const results = [];let remaining = 0, index = 0;for (const item of iterable) {const currentIndex = index++;remaining++;MyPromise.resolve(item).then(value => {results[currentIndex] = { status: 'fulfilled', value };if (--remaining === 0) resolve(results);},reason => {results[currentIndex] = { status: 'rejected', reason };if (--remaining === 0) resolve(results);});}if (index === 0) resolve([]);});
};MyPromise.any = function(iterable) {return new MyPromise((resolve, reject) => {const errors = [];let remaining = 0, index = 0, fulfilled = false;for (const item of iterable) {const currentIndex = index++;remaining++;MyPromise.resolve(item).then(value => {if (!fulfilled) { fulfilled = true; resolve(value); }},reason => {errors[currentIndex] = reason;if (--remaining === 0 && !fulfilled) {reject(new AggregateError(errors, 'All promises were rejected'));}});}if (index === 0) reject(new AggregateError([], 'All promises were rejected'));});
};
7. 常见陷阱与测试用例
- 回调同步抛错要走拒绝分支;
then
多次调用要队列化,每次返回新 promise;then
参数非函数需“透传/抛出”;- 与原生 Promise 混用时的 thenable 兼容。
快速测试:
new MyPromise((resolve) => resolve(1)).then(v => v + 1).then().then(v => { throw new Error('boom ' + v); }).catch(e => 100).finally(() => console.log('done')).then(console.log); // done \n 100
8. 总结与附录
- 你已经实现了一个符合 Promises/A+ 的
MyPromise
,掌握了状态机、解析过程、异步调度与常用静态方法; - 附:规范原文与测试套件:
- Promises/A+ 规范(英文):
https://promisesaplus.com/
- 常见测试库:
promises-aplus-tests
- Promises/A+ 规范(英文):