从零手写一个 Promise:深入理解 JavaScript 异步编程的本质 每个前端和 Node.js 开发者每天都在用 Promise但有多少人真正理解它内部是怎么运转的今天我们不复述 MDN 文档而是从零开始亲手实现一个符合 Promises/A 规范的迷你 Promise把异步编程的本质看个通透。为什么你要关心 Promise 的实现你可能觉得我会用 async/await会用 .then()够了。 但你是否遇到过这些困惑为什么 new Promise 里的代码是立即执行的但 .then() 的回调却不是为什么 Promise 的 .then() 能链式调用而且能 穿透 返回值事件循环里Promise 的微任务到底排在哪个位置这些问题的答案全都藏在 Promise 的实现细节里。理解了它你就不再是 API 的使用者而是异步模型的掌控者。第一步Promise 的状态机模型Promise 的本质是一个有限状态机只有三种状态PENDING ──resolve──→ FULFILLED │ └──reject──→ REJECTED一旦从 PENDING 变成 FULFILLED 或 REJECTED就再也无法改变 —— 这就是 承诺 的含义。让我们先把骨架搭起来class MiniPromise { static PENDING pending; static FULFILLED fulfilled; static REJECTED rejected; constructor(executor) { this.status MiniPromise.PENDING; this.value undefined; // 成功的值 this.reason undefined; // 失败的原因 this.onFulfilledCallbacks []; // 成功回调队列 this.onRejectedCallbacks []; // 失败回调队列 // resolve 和 reject 用来改变状态 const resolve (value) { if (this.status MiniPromise.PENDING) { this.status MiniPromise.FULFILLED; this.value value; // 状态改变后执行所有排队的回调 this.onFulfilledCallbacks.forEach(fn fn()); } }; const reject (reason) { if (this.status MiniPromise.PENDING) { this.status MiniPromise.REJECTED; this.reason reason; this.onRejectedCallbacks.forEach(fn fn()); } }; // executor 立即执行这就是为什么 new Promise 里的代码同步运行 try { executor(resolve, reject); } catch (err) { reject(err); } } }注意一个关键细节executor(resolve, reject) 是在构造函数中同步调用的。这就解释了下面这段代码的行为console.log(1); new MiniPromise((resolve) { console.log(2); resolve(done); }); console.log(3); // 输出1 → 2 → 3 全是同步的第二步实现 .then() —— Promise 的灵魂.then() 是 Promise 最核心的方法它需要处理三种情况调用 .then() 时 Promise 已经 fulfilled → 直接执行 onFulfilled调用 .then() 时 Promise 已经 rejected → 直接执行 onRejected调用 .then() 时 Promise 还在 pending → 把回调存起来等状态改变再执行但还有一个最关键的要求根据 Promises/A 规范.then() 的回调必须异步执行即作为微任务。在浏览器中我们用 queueMicrotask在 Node.js 中可以用 process.nextTick。then(onFulfilled, onRejected) { // 参数穿透如果你不传回调值/错误要能传递给下一个 .then() onFulfilled typeof onFulfilled function ? onFulfilled : value value; onRejected typeof onRejected function ? onRejected : reason { throw reason; }; // 返回一个新的 Promise实现链式调用 const promise2 new MiniPromise((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.status MiniPromise.FULFILLED) { handleFulfilled(); } else if (this.status MiniPromise.REJECTED) { handleRejected(); } else { // 还在 PENDING先排队 this.onFulfilledCallbacks.push(handleFulfilled); this.onRejectedCallbacks.push(handleRejected); } }); return promise2; }这里有两个精妙的设计每次 .then() 都返回一个新 Promise —— 这就是链式调用的秘密。参数穿透 —— value value 和 reason { throw reason } 确保了即使你不写 .catch()错误也能沿着链条传递下去。第三步resolvePromise —— 处理 返回值是 Promise 的情况这是整个实现中最烧脑的部分。.then() 的回调可能返回各种东西普通值、另一个 Promise、甚至是一个 thenable 对象。规范对此有严格定义function resolvePromise(promise2, x, resolve, reject) { // 1. 不能返回自身否则死循环 if (promise2 x) { return reject(new TypeError(Chaining cycle detected!)); } // 2. 如果 x 是 MiniPromise 实例等它决议 if (x instanceof MiniPromise) { x.then(resolve, reject); return; } // 3. 如果 x 是 thenable有 .then 方法的对象/函数 // 这是为了兼容其他 Promise 库如 bluebird、Q 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; // 递归解析因为 resolve 的值可能还是一个 Promise resolvePromise(promise2, y, resolve, reject); }, (r) { if (called) return; called true; reject(r); } ); } else { // x 是个普通对象直接 resolve resolve(x); } } catch (err) { if (called) return; called true; reject(err); } } else { // 4. x 是普通值直接 resolve resolve(x); } }called 标志位是一个防御性编程的细节如果外部 thenable 同时调用了 resolve 和 reject我们只认第一个。第四步补全常用静态方法有了核心骨架静态方法就是水到渠成// Promise.resolve static resolve(value) { if (value instanceof MiniPromise) return value; return new MiniPromise((resolve) resolve(value)); } // Promise.reject static reject(reason) { return new MiniPromise((_, reject) reject(reason)); } // Promise.all —— 全部成功才成功 static all(promises) { return new MiniPromise((resolve, reject) { const results []; let count 0; const list Array.from(promises); if (list.length 0) return resolve([]); list.forEach((p, i) { MiniPromise.resolve(p).then( (value) { results[i] value; // 注意用 i 保证顺序 if (count list.length) resolve(results); }, reject // 任一失败即失败 ); }); }); } // Promise.race —— 谁先决议就用谁 static race(promises) { return new MiniPromise((resolve, reject) { for (const p of promises) { MiniPromise.resolve(p).then(resolve, reject); } }); } // catch 和 finally 只是 then 的语法糖 catch(onRejected) { return this.then(null, onRejected); } finally(callback) { return this.then( value MiniPromise.resolve(callback()).then(() value), reason MiniPromise.resolve(callback()).then(() { throw reason; }) ); }注意 Promise.all 中 results[i] value 这一行 —— 它用索引 i 而非 push 来保证结果数组的顺序与输入一致这是一个容易被忽略的正确性细节。完整测试看看我们的 Promise 能不能跑起来// 测试 1基本链式调用 new MiniPromise((resolve) { setTimeout(() resolve(Hello), 100); }) .then(msg msg World) .then(msg { console.log(msg); // Hello World }); // 测试 2错误穿透 MiniPromise.reject(出错了) .then(v v) // 跳过 .then(v v) // 跳过 .catch(err console.log(捕获:, err)); // 捕获: 出错了 // 测试 3Promise.all MiniPromise.all([ MiniPromise.resolve(1), new MiniPromise(r setTimeout(() r(2), 50)), MiniPromise.resolve(3) ]).then(results { console.log(results); // [1, 2, 3] }); // 测试 4嵌套 Promise递归解析 new MiniPromise(resolve { resolve(new MiniPromise(r setTimeout(() r(深层值), 100))); }).then(v console.log(v)); // 深层值 // 测试 5事件循环验证 console.log(同步 1); new MiniPromise(r { console.log(同步 2); r(); }).then(() console.log(微任务)); console.log(同步 3); // 输出顺序同步 1 → 同步 2 → 同步 3 → 微任务回到最初的问题现在你可以回答开头的三个问题了Q1为什么 new Promise 里的代码立即执行但 .then() 的回调不是因为 executor(resolve, reject) 在构造函数中是同步调用的而 .then() 的回调被包裹在 queueMicrotask() 中作为微任务异步调度。Q2为什么 .then() 能链式调用并穿透返回值每次 .then() 都返回一个新的 Promise。回调的返回值会通过 resolvePromise 递归解析如果是普通值就 resolve如果是 Promise 就等它决议。穿透 是参数默认函数 value value 的功劳。Q3Promise 微任务在事件循环中的位置微任务队列在当前宏任务执行完毕后、下一个宏任务开始前清空。所以 .then() 回调总是在 setTimeout 之前执行。更进一步async/await 只是 Promise 的语法糖当你理解了 Promise 的内部机制async/await 就不再神秘// async/await async function fetchUser() { const res await fetch(/api/user); const data await res.json(); return data; } // 等价的 Promise 写法 function fetchUser() { return fetch(/api/user).then(res res.json()); }async 函数本质上是一个返回 Promise 的函数await 就是 .then() 的语法糖 —— 它把后续的代码放进了 .then() 的回调里。JavaScript 引擎帮你做了这个 编译 工作。总结我们用大约 150 行代码实现了一个基本完整的 Promise。回顾核心设计设计要素实现方式状态机PENDING → FULFILLED / REJECTED单向不可逆异步执行queueMicrotask 将回调放入微任务队列链式调用每次 .then() 返回新 Promise递归解析resolvePromise 处理返回值是 Promise 的情况错误穿透默认回调 reason { throw reason }兼容性thenable 协议检查 .then 方法读源码最好的方式不是去看别人的分析文章而是自己写一遍。 当你亲手处理了每一个边界情况那些曾经让你困惑的异步行为都会变成理所当然。