1. Promise 实现原理与手写指南
作为现代 JavaScript 异步编程的核心,Promise 早已成为每个前端开发者必须掌握的技能点。但真正理解 Promise 的内部机制,并能从零实现一个符合规范的 Promise 类,才是区分"会用"和"精通"的关键分水岭。本文将带你深入 Promise 的实现细节,手把手教你写出一个工业级可用的 MyPromise 类。
2. Promise/A+ 规范解析
2.1 核心状态机设计
Promise 本质上是一个状态机,包含三个基本状态:
- pending:初始状态,既不是成功也不是失败
- fulfilled:操作成功完成
- rejected:操作失败
状态转换是不可逆的,一旦从 pending 变为 fulfilled 或 rejected,就不能再改变状态。这是 Promise 可靠性的基石。
class MyPromise { constructor(executor) { this.state = 'pending'; this.value = undefined; this.reason = undefined; this.onFulfilledCallbacks = []; this.onRejectedCallbacks = []; const resolve = (value) => { if (this.state === 'pending') { this.state = 'fulfilled'; this.value = value; this.onFulfilledCallbacks.forEach(fn => fn()); } }; const reject = (reason) => { if (this.state === 'pending') { this.state = 'rejected'; this.reason = reason; this.onRejectedCallbacks.forEach(fn => fn()); } }; try { executor(resolve, reject); } catch (err) { reject(err); } } }2.2 then 方法的实现要点
then 方法是 Promise 链式调用的核心,需要处理以下情况:
- 当前 Promise 已完成:直接执行回调
- 当前 Promise 未完成:将回调存入队列
- 返回值穿透:如果 then 没有提供回调,值需要透传给下一个 Promise
then(onFulfilled, onRejected) { // 参数校验和默认值处理 onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }; const promise2 = new MyPromise((resolve, reject) => { if (this.state === 'fulfilled') { setTimeout(() => { try { const x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }, 0); } else if (this.state === 'rejected') { setTimeout(() => { try { const x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }, 0); } else { this.onFulfilledCallbacks.push(() => { setTimeout(() => { try { const x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }, 0); }); this.onRejectedCallbacks.push(() => { setTimeout(() => { try { const x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }, 0); }); } }); return promise2; }3. 完整实现与关键细节
3.1 resolvePromise 的实现
这是 Promise 实现中最复杂的部分,需要处理各种返回值情况:
- 普通值:直接 resolve
- Promise 对象:等待其状态改变
- thenable 对象:按 Promise 处理
function resolvePromise(promise2, x, resolve, reject) { // 防止循环引用 if (promise2 === x) { return reject(new TypeError('Chaining cycle detected for promise')); } // 防止重复调用 let called = false; if (x !== null && (typeof x === 'object' || typeof x === 'function')) { 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 (e) { if (called) return; called = true; reject(e); } } else { resolve(x); } }3.2 其他必要方法实现
一个完整的 Promise 实现还需要以下方法:
// catch 方法实际上是 then 的语法糖 catch(onRejected) { return this.then(null, onRejected); } // finally 方法无论成功失败都会执行 finally(callback) { return this.then( value => MyPromise.resolve(callback()).then(() => value), reason => MyPromise.resolve(callback()).then(() => { throw reason }) ); } // 静态方法 resolve static resolve(value) { if (value instanceof MyPromise) { return value; } return new MyPromise(resolve => resolve(value)); } // 静态方法 reject static reject(reason) { return new MyPromise((_, reject) => reject(reason)); } // 静态方法 all static all(promises) { return new MyPromise((resolve, reject) => { const results = []; let count = 0; promises.forEach((promise, index) => { MyPromise.resolve(promise).then( value => { results[index] = value; count++; if (count === promises.length) resolve(results); }, reject ); }); }); } // 静态方法 race static race(promises) { return new MyPromise((resolve, reject) => { promises.forEach(promise => { MyPromise.resolve(promise).then(resolve, reject); }); }); }4. 常见问题与调试技巧
4.1 异步执行顺序问题
Promise 的回调总是异步执行的,即使 Promise 已经 resolved。这是规范要求的,确保执行顺序的一致性。
console.log(1); new MyPromise(resolve => { console.log(2); resolve(3); }).then(console.log); console.log(4); // 正确输出顺序:1, 2, 4, 34.2 错误捕获陷阱
Promise 内部的错误如果不被捕获,会导致静默失败。最佳实践是始终添加 catch 处理。
// 不好的写法 new MyPromise((_, reject) => reject('error')); // 好的写法 new MyPromise((_, reject) => reject('error')) .catch(e => console.error('Caught:', e));4.3 内存泄漏排查
未处理的 Promise 拒绝可能导致内存泄漏。在 Node.js 中可以这样监听:
process.on('unhandledRejection', (reason, promise) => { console.error('Unhandled Rejection at:', promise, 'reason:', reason); });5. 性能优化与实践建议
5.1 微任务队列优化
现代浏览器和 Node.js 使用微任务队列实现 Promise。手写实现时使用 setTimeout 模拟,但真实环境中性能更好。
5.2 链式调用深度限制
过长的 Promise 链可能导致栈溢出。解决方案是定期使用 setTimeout 打破调用栈:
function runNextTick(fn) { return new Promise(resolve => { setTimeout(() => resolve(fn()), 0); }); } async function longRunningTask() { let result = await initialTask(); for (let i = 0; i < 1000; i++) { result = await runNextTick(() => nextStep(result)); } return result; }5.3 Promise 池控制
当处理大量并发 Promise 时,需要控制并发数量:
class PromisePool { constructor(maxConcurrent) { this.maxConcurrent = maxConcurrent; this.queue = []; this.running = 0; } add(task) { return new Promise((resolve, reject) => { this.queue.push({ task, resolve, reject }); this.run(); }); } run() { while (this.running < this.maxConcurrent && this.queue.length) { const { task, resolve, reject } = this.queue.shift(); this.running++; task() .then(resolve) .catch(reject) .finally(() => { this.running--; this.run(); }); } } }6. 测试与验证
6.1 使用 Promises/A+ 测试套件
官方测试套件可以验证你的实现是否符合规范:
npm install promises-aplus-tests -D创建适配器文件:
const MyPromise = require('./my-promise'); module.exports = { resolved: MyPromise.resolve, rejected: MyPromise.reject, deferred() { const result = {}; result.promise = new MyPromise((resolve, reject) => { result.resolve = resolve; result.reject = reject; }); return result; } };然后运行测试:
promises-aplus-tests adapter.js6.2 边界条件测试案例
确保测试以下边界条件:
- 在 executor 中同步 resolve
- 在 executor 中异步 reject
- then 方法多次调用
- then 方法链式调用返回 Promise
- 循环引用检测
// 循环引用测试 const p1 = new MyPromise(resolve => { const p2 = new MyPromise(resolve => resolve(p1)); resolve(p2); }); p1.catch(e => console.error('Caught cycle:', e.message));7. 进阶实现技巧
7.1 取消功能扩展
原生 Promise 不支持取消,但我们可以扩展实现:
class CancelablePromise extends MyPromise { constructor(executor) { let _reject; super((resolve, reject) => { _reject = reject; executor(resolve, reject); }); this.cancel = () => { _reject(new Error('Promise canceled')); }; } } // 使用示例 const p = new CancelablePromise(resolve => { setTimeout(() => resolve('done'), 1000); }); p.catch(e => console.log(e.message)); // 'Promise canceled' p.cancel();7.2 进度通知扩展
某些场景需要进度通知:
class ProgressPromise extends MyPromise { constructor(executor) { const progressCallbacks = []; super((resolve, reject) => { executor( resolve, reject, progress => { progressCallbacks.forEach(cb => cb(progress)); } ); }); this.progress = cb => { progressCallbacks.push(cb); return this; }; } } // 使用示例 new ProgressPromise((resolve, reject, progress) => { let percent = 0; const interval = setInterval(() => { percent += 10; progress(percent); if (percent >= 100) { clearInterval(interval); resolve(); } }, 200); }) .progress(p => console.log(`${p}% complete`)) .then(() => console.log('Done'));7.3 超时控制实现
为 Promise 添加超时功能:
MyPromise.prototype.timeout = function(ms, message = 'Promise timeout') { return MyPromise.race([ this, new MyPromise((_, reject) => setTimeout(() => reject(new Error(message)), ms) ) ]); }; // 使用示例 new MyPromise(resolve => setTimeout(resolve, 2000, 'slow') ) .timeout(1000) .catch(e => console.log(e.message)); // 'Promise timeout'8. 与 async/await 的配合
8.1 在 async 函数中使用
手写的 MyPromise 完全可以与 async/await 语法配合使用:
async function example() { try { const result = await new MyPromise(resolve => setTimeout(resolve, 100, 'done') ); console.log(result); // 'done' } catch (e) { console.error(e); } }8.2 实现 async 辅助函数
基于 MyPromise 实现常用的 async 辅助函数:
function delay(ms, value) { return new MyPromise(resolve => setTimeout(resolve, ms, value) ); } function timeout(promise, ms, message = 'Timeout') { return MyPromise.race([ promise, delay(ms).then(() => { throw new Error(message) }) ]); } async function retry(fn, retries = 3, delayMs = 1000) { try { return await fn(); } catch (e) { if (retries <= 0) throw e; await delay(delayMs); return retry(fn, retries - 1, delayMs); } }9. 浏览器与 Node.js 环境差异
9.1 微任务队列差异
- 浏览器:使用 MutationObserver 或微任务队列
- Node.js:使用 process.nextTick
在手写实现中,我们使用 setTimeout 模拟,但在实际环境中:
// 环境敏感的异步调度 const nextTick = (() => { if (typeof process === 'object' && process.nextTick) { return process.nextTick; } if (typeof MutationObserver !== 'undefined') { let counter = 1; const textNode = document.createTextNode(String(counter)); const observer = new MutationObserver(() => { callback(); counter = (counter + 1) % 2; textNode.data = String(counter); }); observer.observe(textNode, { characterData: true }); return callback => { textNode.data = String((counter = (counter + 1) % 2)); }; } return callback => setTimeout(callback, 0); })();9.2 未处理拒绝警告
Node.js 有专门的 unhandledRejection 事件,而浏览器环境需要 polyfill:
if (typeof window !== 'undefined') { window.addEventListener('unhandledrejection', event => { console.warn('Unhandled rejection:', event.reason); event.preventDefault(); }); }10. 实际应用场景
10.1 请求队列管理
实现一个带并发控制的请求队列:
class RequestQueue { constructor(concurrency = 4) { this.queue = []; this.active = 0; this.concurrency = concurrency; } add(requestFn) { return new MyPromise((resolve, reject) => { this.queue.push({ requestFn, resolve, reject }); this.next(); }); } next() { if (this.active >= this.concurrency || !this.queue.length) return; this.active++; const { requestFn, resolve, reject } = this.queue.shift(); MyPromise.resolve(requestFn()) .then(resolve) .catch(reject) .finally(() => { this.active--; this.next(); }); } } // 使用示例 const queue = new RequestQueue(2); for (let i = 0; i < 10; i++) { queue.add(() => fetch(`/api/data/${i}`)) .then(response => console.log('Got:', i)); }10.2 数据库事务封装
用 Promise 封装数据库事务:
class Database { beginTransaction() { return new MyPromise((resolve, reject) => { this.connection.beginTransaction(err => { if (err) return reject(err); resolve(); }); }); } query(sql, params) { return new MyPromise((resolve, reject) => { this.connection.query(sql, params, (err, results) => { if (err) return reject(err); resolve(results); }); }); } commit() { return new MyPromise((resolve, reject) => { this.connection.commit(err => { if (err) return this.rollback().finally(() => reject(err)); resolve(); }); }); } rollback() { return new MyPromise(resolve => { this.connection.rollback(() => resolve()); }); } async executeInTransaction(callback) { await this.beginTransaction(); try { const result = await callback(); await this.commit(); return result; } catch (e) { await this.rollback(); throw e; } } }11. 性能对比与优化
11.1 原生 Promise vs 手写实现
在大多数现代 JavaScript 引擎中,原生 Promise 经过高度优化:
- V8 使用微任务队列和内部优化
- 手写实现通常慢 2-5 倍
11.2 内存占用分析
Promise 链会保持引用直到完成,长链可能导致内存压力。解决方案:
- 定期打断长链
- 避免在 Promise 中保存大对象
// 不好的做法:保持大对象引用 function processLargeData(data) { return validate(data) .then(() => transform(data)) // 保持 data 引用 .then(result => save(result)); } // 好的做法:尽早释放 function processLargeData(data) { return validate(data) .then(() => { const result = transform(data); data = null; // 显式释放 return result; }) .then(save); }12. 调试与错误追踪
12.1 增强错误堆栈
Promise 链可能导致错误堆栈不完整,可以增强:
class TraceablePromise extends MyPromise { constructor(executor) { const trace = new Error().stack.split('\n').slice(2).join('\n'); super((resolve, reject) => { executor( value => resolve(value), reason => { if (reason instanceof Error && !reason.stack.includes(trace)) { reason.stack += '\n--- Promise created at ---\n' + trace; } reject(reason); } ); }); } }12.2 异步上下文跟踪
使用 AsyncLocalStorage(Node.js)跟踪异步调用链:
const { AsyncLocalStorage } = require('async_hooks'); const asyncLocalStorage = new AsyncLocalStorage(); class ContextAwarePromise extends MyPromise { constructor(executor) { const context = asyncLocalStorage.getStore(); super((resolve, reject) => { asyncLocalStorage.run(context, () => { executor(resolve, reject); }); }); } }13. 与 Generator 的配合
13.1 自动执行 Generator
实现类似 co 库的功能:
function runGenerator(gen) { return new MyPromise((resolve, reject) => { const g = gen(); function step(value) { try { const { done, value: result } = g.next(value); if (done) return resolve(result); MyPromise.resolve(result).then( val => step(val), err => g.throw(err) ); } catch (e) { reject(e); } } step(); }); } // 使用示例 runGenerator(function* () { const a = yield new MyPromise(resolve => setTimeout(resolve, 100, 1)); const b = yield new MyPromise(resolve => setTimeout(resolve, 100, 2)); return a + b; }).then(console.log); // 313.2 实现 async/await 的 polyfill
基于 Generator 和 Promise 实现 async/await:
function asyncToGenerator(fn) { return function(...args) { return runGenerator(fn.bind(this, ...args)); }; } // 使用示例 const asyncFunc = asyncToGenerator(function*(a, b) { const x = yield new MyPromise(resolve => setTimeout(resolve, 100, a)); const y = yield new MyPromise(resolve => setTimeout(resolve, 100, b)); return x + y; }); asyncFunc(3, 4).then(console.log); // 714. 测试驱动开发实践
14.1 单元测试策略
为 Promise 实现编写全面的单元测试:
describe('MyPromise', () => { it('should resolve with value', done => { new MyPromise(resolve => resolve(42)) .then(value => { assert.equal(value, 42); done(); }); }); it('should reject with reason', done => { new MyPromise((_, reject) => reject('error')) .catch(reason => { assert.equal(reason, 'error'); done(); }); }); it('should chain promises', done => { new MyPromise(resolve => resolve(1)) .then(x => x + 1) .then(x => x * 2) .then(value => { assert.equal(value, 4); done(); }); }); it('should handle async operations', done => { new MyPromise(resolve => setTimeout(() => resolve('async'), 50) ).then(value => { assert.equal(value, 'async'); done(); }); }); });14.2 压力测试方案
测试 Promise 实现的稳定性和性能:
function stressTest(count = 10000) { return new MyPromise(resolve => { let completed = 0; const start = Date.now(); for (let i = 0; i < count; i++) { new MyPromise(r => r(i)) .then(x => x * 2) .then(x => { completed++; if (completed === count) { resolve(Date.now() - start); } }); } }); } stressTest().then(duration => { console.log(`Completed in ${duration}ms`); });15. 与其他异步模式的对比
15.1 回调函数 vs Promise
回调地狱问题:
// 回调地狱 getData(a => { processA(a, b => { processB(b, c => { processC(c, d => { console.log(d); }); }); }); }); // Promise 解决方案 getData() .then(processA) .then(processB) .then(processC) .then(console.log);15.2 EventEmitter vs Promise
事件驱动与 Promise 的对比:
- EventEmitter:多个监听者,多次触发
- Promise:单个结果,一次性解决
// 混合使用示例 function eventToPromise(emitter, event) { return new MyPromise(resolve => { emitter.once(event, resolve); }); } // 使用示例 const fs = require('fs'); const stream = fs.createReadStream('file.txt'); eventToPromise(stream, 'open') .then(() => console.log('File opened')) .catch(console.error);16. 浏览器兼容性处理
16.1 旧版浏览器支持
在不支持 Promise 的环境中使用 polyfill:
if (typeof Promise === 'undefined') { window.Promise = MyPromise; }16.2 与 jQuery Deferred 互操作
jQuery Deferred 对象可以转换为 Promise:
function jQueryToPromise(deferred) { return new MyPromise((resolve, reject) => { deferred.then(resolve, reject); }); } // 使用示例 const jqDeferred = $.ajax('/api/data'); jQueryToPromise(jqDeferred) .then(data => console.log(data));17. 安全注意事项
17.1 防止 Promise 劫持
确保 then 方法不会被恶意修改:
class SecurePromise extends MyPromise { then(onFulfilled, onRejected) { const originalThen = super.then; return originalThen.call( this, typeof onFulfilled === 'function' ? onFulfilled : null, typeof onRejected === 'function' ? onRejected : null ); } }17.2 输入验证强化
对 executor 函数进行严格验证:
class ValidatedPromise extends MyPromise { constructor(executor) { if (typeof executor !== 'function') { throw new TypeError('Promise executor must be a function'); } let called = false; super((resolve, reject) => { try { executor( value => { if (called) return; called = true; resolve(value); }, reason => { if (called) return; called = true; reject(reason); } ); } catch (e) { if (!called) { called = true; reject(e); } } }); } }18. 性能监控与调优
18.1 Promise 执行时间统计
监控 Promise 的执行时间:
function timedPromise(promise, name = 'unknown') { const start = performance.now(); return promise.then( value => { console.log(`Promise ${name} resolved in ${performance.now() - start}ms`); return value; }, reason => { console.error(`Promise ${name} rejected after ${performance.now() - start}ms`); throw reason; } ); } // 使用示例 timedPromise( new MyPromise(resolve => setTimeout(resolve, 100)), 'delayed' );18.2 内存泄漏检测
跟踪 Promise 生命周期:
const activePromises = new Set(); class TrackedPromise extends MyPromise { constructor(executor) { super((resolve, reject) => { activePromises.add(this); executor( value => { activePromises.delete(this); resolve(value); }, reason => { activePromises.delete(this); reject(reason); } ); }); } } // 检查泄漏 setInterval(() => { console.log(`Active promises: ${activePromises.size}`); }, 5000);19. 与 TypeScript 的集成
19.1 类型定义
为 MyPromise 添加 TypeScript 类型:
interface Thenable<T> { then<TResult1 = T, TResult2 = never>( onFulfilled?: ((value: T) => TResult1 | Thenable<TResult1>) | null, onRejected?: ((reason: any) => TResult2 | Thenable<TResult2>) | null ): Thenable<TResult1 | TResult2>; } class MyPromise<T> implements Thenable<T> { constructor( executor: ( resolve: (value: T | Thenable<T>) => void, reject: (reason?: any) => void ) => void ) { // 实现... } then<TResult1 = T, TResult2 = never>( onFulfilled?: ((value: T) => TResult1 | Thenable<TResult1>) | null, onRejected?: ((reason: any) => TResult2 | Thenable<TResult2>) | null ): MyPromise<TResult1 | TResult2> { // 实现... } catch<TResult = never>( onRejected?: ((reason: any) => TResult | Thenable<TResult>) | null ): MyPromise<T | TResult> { return this.then(null, onRejected); } }19.2 泛型应用
利用 TypeScript 泛型增强类型安全:
function fetchUser(id: number): MyPromise<{ name: string }> { return new MyPromise(resolve => { setTimeout(() => resolve({ name: `User ${id}` }), 100); }); } fetchUser(1).then(user => { console.log(user.name.toUpperCase()); // 类型安全 });20. 总结与最佳实践
20.1 核心要点回顾
- Promise 是一个状态机,有三种状态:pending、fulfilled、rejected
- then 方法必须返回新的 Promise 以实现链式调用
- 需要处理各种返回值情况,包括 thenable 对象
- 错误必须被捕获,否则会导致静默失败
20.2 推荐实践
- 总是返回或终止 Promise 链
- 使用 catch 处理错误
- 避免在 Promise 中抛出同步错误
- 对长时间运行的 Promise 添加超时控制
- 在适当的时候使用 Promise.all 并行处理
20.3 进一步学习方向
- 阅读 Promises/A+ 规范原文
- 研究主流 Promise 库的源码(如 bluebird)
- 学习 async/await 的实现原理
- 探索 RxJS 等响应式编程库
通过手写实现 Promise,不仅能深入理解异步编程的本质,还能掌握 JavaScript 的核心工作机制。这种底层知识对于解决复杂的异步问题和性能优化至关重要。