
1. 为什么我们需要手写Promise2009年当Node.js首次将JavaScript带入服务端开发领域时回调地狱Callback Hell问题就成为了每个JS开发者心中的痛。层层嵌套的回调函数不仅让代码难以阅读更给错误处理和流程控制带来了巨大挑战。直到ES6正式推出Promise规范这种局面才得到根本性改变。我在2016年接手一个遗留项目时曾见过这样的代码fs.readFile(config.json, function(err, data) { if (err) return console.error(err); parseXML(data, function(err, result) { if (err) return console.error(err); db.query(SELECT..., function(err, rows) { if (err) return console.error(err); // 更多嵌套... }); }); });这段代码暴露了回调模式的三个致命缺陷错误处理重复且分散执行流程难以追踪异步操作无法并行管理2. Promise核心机制解析2.1 状态机的本质Promise本质上是一个状态机包含三个不可逆的状态转换pending → fulfilled通过resolvepending → rejected通过reject我曾用简单的对象模拟过这个机制const fakePromise { state: pending, value: null, reason: null, resolve: function(value) { if (this.state ! pending) return; this.state fulfilled; this.value value; }, reject: function(reason) { if (this.state ! pending) return; this.state rejected; this.reason reason; } }2.2 微任务的奥秘Promise的回调执行时机是个容易误解的点。在Chrome中实测以下代码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这是因为Promise回调属于微任务microtask而setTimeout是宏任务macrotask。事件循环会优先清空微任务队列。3. 完整实现手写Promise3.1 基础框架搭建我们先构建最简化的Promise骨架class MyPromise { constructor(executor) { this.state pending; this.value undefined; this.reason undefined; const resolve (value) { if (this.state ! pending) return; this.state fulfilled; this.value value; }; const reject (reason) { if (this.state ! pending) return; this.state rejected; this.reason reason; }; try { executor(resolve, reject); } catch (err) { reject(err); } } }这个基础版本已经可以处理同步操作const p new MyPromise((resolve) { resolve(42); });3.2 then方法实现then方法是Promise最复杂的部分需要考虑状态未完成时的回调暂存链式调用支持值穿透处理完整实现then(onFulfilled, onRejected) { // 值穿透处理 onFulfilled typeof onFulfilled function ? onFulfilled : v v; onRejected typeof onRejected function ? onRejected : err { throw err }; const promise2 new MyPromise((resolve, reject) { const handle () { queueMicrotask(() { try { const x this.state fulfilled ? onFulfilled(this.value) : onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (err) { reject(err); } }); }; if (this.state ! pending) { handle(); } else { this.onFulfilledCallbacks.push(() handle()); this.onRejectedCallbacks.push(() handle()); } }); return promise2; }3.3 Promise解决过程这是符合Promises/A规范的核心function resolvePromise(promise2, x, resolve, reject) { // 防止循环引用 if (x promise2) { return reject(new TypeError(Chaining cycle detected)); } // 处理thenable对象 if ((typeof x object x ! null) || 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; resolvePromise(promise2, y, resolve, reject); }, r { if (called) return; called true; reject(r); } ); } catch (err) { if (!called) reject(err); } } else { resolve(x); } } else { resolve(x); } }4. 高级功能实现4.1 静态方法实现Promise.resolve的polyfillstatic resolve(value) { if (value instanceof MyPromise) return value; return new MyPromise(resolve resolve(value)); }Promise.reject的简单实现static reject(reason) { return new MyPromise((_, reject) reject(reason)); }4.2 all/race方法Promise.all的典型实现static all(promises) { return new MyPromise((resolve, reject) { const results []; let count 0; const processResult (i, val) { results[i] val; if (count promises.length) resolve(results); }; promises.forEach((p, i) { MyPromise.resolve(p).then( val processResult(i, val), reject ); }); }); }Promise.race的实现更简单static race(promises) { return new MyPromise((resolve, reject) { promises.forEach(p { MyPromise.resolve(p).then(resolve, reject); }); }); }5. 常见问题与调试技巧5.1 异步测试陷阱测试Promise时常见的一个错误是忘记测试是异步的// 错误示例 it(should resolve, () { const p new MyPromise(resolve resolve(42)); p.then(val { expect(val).toBe(42); // 这行可能不会执行 }); }); // 正确写法 it(should resolve, done { const p new MyPromise(resolve resolve(42)); p.then(val { expect(val).toBe(42); done(); }); });5.2 内存泄漏排查在实现过程中我曾遇到内存泄漏问题。通过Chrome DevTools的Memory面板发现未完成的Promise会保持对回调函数的引用。解决方案是// 在适当的时候清理回调数组 this.onFulfilledCallbacks null; this.onRejectedCallbacks null;5.3 性能优化点使用微任务队列替代setTimeout// 优先使用现代API if (typeof queueMicrotask function) { queueMicrotask(fn); } else { Promise.resolve().then(fn); }避免不必要的Promise封装// 不推荐 new Promise(resolve resolve(someValue)); // 推荐 Promise.resolve(someValue);6. 从实现中学到的经验在完整实现Promise的过程中最深刻的体会是错误处理的重要性。最初版本我忽略了这两个关键点executor中的同步错误必须捕获try { executor(resolve, reject); } catch (err) { reject(err); }then方法中的回调执行也需要try-catchtry { const x onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch (err) { reject(err); }另一个收获是对异步编程本质的理解。通过实现Promise我真正明白了事件循环中微任务的优先级状态不可变性的重要性链式调用的实现原理这些知识在调试复杂异步流程时非常有用。比如当遇到Promise似乎没有执行的情况时现在我会首先检查是否忘记return新的Promise是否有未捕获的异常微任务队列是否被阻塞