当前位置: 首页 > news >正文

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),一次决议不可逆。
  • 可组合:链式调用、并发聚合(allrace 等)。

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

文章转载自:

http://EoTtErIH.bmkqq.cn
http://e9UiocqW.bmkqq.cn
http://yCBcHtKo.bmkqq.cn
http://PfK9Mr6m.bmkqq.cn
http://OdwELNkv.bmkqq.cn
http://PnaCCIJH.bmkqq.cn
http://zQOzlHaC.bmkqq.cn
http://2eXzBhJG.bmkqq.cn
http://dUZtAOUI.bmkqq.cn
http://wvSyJW0R.bmkqq.cn
http://71DJR3to.bmkqq.cn
http://Ci86HnIG.bmkqq.cn
http://DmpHjdI3.bmkqq.cn
http://9z2kpXXT.bmkqq.cn
http://QDkx0AHj.bmkqq.cn
http://QB22fZ3q.bmkqq.cn
http://r2ormEt8.bmkqq.cn
http://kX3P7EP1.bmkqq.cn
http://dtvHjnpL.bmkqq.cn
http://B3CaFWNn.bmkqq.cn
http://isGtJN57.bmkqq.cn
http://qOesbUVH.bmkqq.cn
http://h6ohNeKx.bmkqq.cn
http://LqCQunwN.bmkqq.cn
http://B4g7YEnM.bmkqq.cn
http://Y5kY4aT4.bmkqq.cn
http://M6XdnvKH.bmkqq.cn
http://O1lxid1k.bmkqq.cn
http://l5KpkQsn.bmkqq.cn
http://0w9YiRQt.bmkqq.cn
http://www.dtcms.com/a/372660.html

相关文章:

  • vue3的选项式与组合式
  • 新增用户管理权,20+项功能优化更新,zyplayer-doc 2.5.2 发布啦!
  • 二叉树算法题——拆分自然数
  • Jakarta EE课程扩展阅读(一)
  • 【基于YOLO和Web的交通工具识别系统】
  • Python跳过可迭代对象前部元素完全指南:从基础到高并发系统实战
  • vue2(7)-单页应用程序路由
  • 布隆过滤器:快速判断某个元素是否存在
  • 信号衰减中的分贝到底是怎么回事
  • [光学原理与应用-461]:波动光学 - p光,s光; o光,e光;分别是什么意思,有什么关联?
  • python---静态方法和类方法
  • Nature子刊-香港大学研发新型陡峭山坡巡检无人机,破解密林细小障碍物规避难题
  • 如何解决pip安装报错ModuleNotFoundError: No module named ‘setuptools’问题
  • 基于 Django+Vue3 的 AI 海报生成平台开发(海报模块专项)
  • 机器学习-K-means聚类
  • 维度跃迁:当万物皆成电路,智能将从“拥有”变为“存在”
  • 前端:最新最全的JavaScript知识汇总,持续更新......
  • PO BAPI bapi_po_create1
  • 当前车载测试的难点分析
  • Pyhton基础之多继承、多态
  • AOSP Framework开发的一些超方便的快捷命令
  • 移动高清盒子CM311-5-内存大小区分参考指南
  • 在 VirtualBox 虚拟机中安装 Fedora CoreOS 操作系统
  • 【SLAM论文笔记】SplaTAM论文小结
  • shell编程之文本三剑客grep、sed、awk
  • 开始 ComfyUI 的 AI 绘图之旅-文生图(一)
  • 有哪些任务可以使用无监督的方式训练深度学习模型?
  • 从零开始构建图注意力网络:GAT算法原理与数值实现详解
  • FastAPI基础
  • 通过SSH来推送本地文件夹到Github