深入理解JavaScript Promise实现原理与手写实践
1. 为什么需要手写Promise全家桶?
三年前我面试字节跳动时,面试官让我在白板上实现Promise.all,当时手忙脚乱没能完整写出来。这件事让我意识到,真正理解Promise的实现原理比单纯会用API更重要。如今作为团队技术面试官,我发现80%的候选人对Promise的理解停留在表面用法。
Promise是现代JavaScript异步编程的基石。根据State of JS 2023调查报告,98%的前端项目都在使用Promise,但只有23%的开发者能说清楚它的实现机制。手写Promise全家桶不仅能帮你:
- 深入理解Event Loop和微任务机制
- 掌握异步编程的核心设计模式
- 在面试中展现扎实的JS功底
- 提升复杂异步流程的控制能力
2. Promise核心实现原理
2.1 基础版Promise构造函数
我们先实现最基础的Promise构造函数,需要处理以下核心逻辑:
class MyPromise { constructor(executor) { this.state = 'pending' // pending/fulfilled/rejected 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) } } }关键点说明:
- 三种状态转换是单向不可逆的(pending → fulfilled/rejected)
- 使用发布-订阅模式处理异步回调
- executor中的异常会自动转为rejected状态
2.2 then方法的完整实现
then方法是Promise最复杂的部分,需要处理以下情况:
- 同步resolve/reject
- 异步resolve/reject
- then链式调用
- 值穿透特性
then(onFulfilled, onRejected) { // 值穿透处理 onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err } 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 }这里有几个关键设计:
- 使用setTimeout确保异步执行(模拟微任务)
- resolvePromise函数处理thenable对象和循环引用
- 错误处理统一用try-catch包裹
2.3 resolvePromise的实现
这是Promise/A+规范中最复杂的部分:
function resolvePromise(promise2, x, resolve, reject) { if (promise2 === x) { return reject(new TypeError('Chaining cycle detected for promise')) } let called = false if ((typeof x === 'object' && x !== null) || 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) } }这个函数需要处理:
- 循环引用检测
- thenable对象适配
- 防止多次调用
- 递归解析Promise
3. Promise静态方法实现
3.1 Promise.resolve / Promise.reject
static resolve(value) { if (value instanceof MyPromise) { return value } return new MyPromise(resolve => resolve(value)) } static reject(reason) { return new MyPromise((_, reject) => reject(reason)) }3.2 Promise.all的实现
面试最高频的问题之一,需要处理:
- 数组成员非Promise的转换
- 按顺序返回结果
- 快速失败机制
static all(promises) { return new MyPromise((resolve, reject) => { const result = [] let count = 0 const processResult = (index, value) => { result[index] = value if (++count === promises.length) { resolve(result) } } for (let i = 0; i < promises.length; i++) { MyPromise.resolve(promises[i]).then( value => processResult(i, value), reject ) } }) }3.3 Promise.race的实现
static race(promises) { return new MyPromise((resolve, reject) => { for (const p of promises) { MyPromise.resolve(p).then(resolve, reject) } }) }3.4 Promise.allSettled的实现
ES2020新增方法,与all的区别在于不会reject:
static allSettled(promises) { return new MyPromise(resolve => { const result = [] let count = 0 const processResult = (index, status, value) => { result[index] = { status, [status === 'fulfilled' ? 'value' : 'reason']: value } if (++count === promises.length) { resolve(result) } } for (let i = 0; i < promises.length; i++) { MyPromise.resolve(promises[i]).then( value => processResult(i, 'fulfilled', value), reason => processResult(i, 'rejected', reason) ) } }) }4. 高级功能实现
4.1 Promise.prototype.finally
finally(callback) { return this.then( value => MyPromise.resolve(callback()).then(() => value), reason => MyPromise.resolve(callback()).then(() => { throw reason }) ) }4.2 Promise.prototype.catch
catch(onRejected) { return this.then(null, onRejected) }4.3 Promise.any的实现
ES2021新增方法,与race的区别在于只关注第一个成功的Promise:
static any(promises) { return new MyPromise((resolve, reject) => { const errors = [] let count = 0 for (let i = 0; i < promises.length; i++) { MyPromise.resolve(promises[i]).then( resolve, reason => { errors[i] = reason if (++count === promises.length) { reject(new AggregateError(errors, 'All promises were rejected')) } } ) } }) }5. 常见面试问题与优化
5.1 如何实现真正的微任务?
现代浏览器中可以用MutationObserver模拟:
function nextTick(callback) { const observer = new MutationObserver(callback) const textNode = document.createTextNode('1') observer.observe(textNode, { characterData: true }) textNode.data = '2' }5.2 如何处理thenable对象?
const thenable = { then(resolve) { resolve('thenable value') } } MyPromise.resolve(thenable).then(console.log) // 输出'thenable value'5.3 性能优化建议
- 使用对象池管理Promise实例
- 避免不必要的Promise封装
- 合理控制并发数量
- 使用AbortController取消异步操作
6. 完整测试用例
验证我们的实现是否符合Promise/A+规范:
// 测试基础功能 const p1 = new MyPromise((resolve) => resolve(1)) p1.then(console.log) // 1 // 测试异步 const p2 = new MyPromise((resolve) => { setTimeout(() => resolve(2), 1000) }) p2.then(console.log) // 1秒后输出2 // 测试链式调用 MyPromise.resolve(3) .then(v => v * 2) .then(console.log) // 6 // 测试all方法 MyPromise.all([ MyPromise.resolve(1), 2, // 非Promise值 new MyPromise(resolve => setTimeout(() => resolve(3), 100)) ]).then(console.log) // [1, 2, 3]7. 实际应用场景
7.1 请求竞速
function fetchWithTimeout(url, timeout) { return Promise.race([ fetch(url), new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeout) ) ]) }7.2 批量请求控制
async function batchRequests(urls, concurrency = 5) { const results = [] const executing = new Set() for (const url of urls) { const p = fetch(url).then(res => res.json()) results.push(p) executing.add(p) p.finally(() => executing.delete(p)) if (executing.size >= concurrency) { await Promise.race(executing) } } return Promise.all(results) }7.3 异步任务队列
class AsyncQueue { constructor() { this.queue = [] this.processing = false } add(task) { return new Promise((resolve, reject) => { this.queue.push({ task, resolve, reject }) if (!this.processing) this.process() }) } async process() { this.processing = true while (this.queue.length) { const { task, resolve, reject } = this.queue.shift() try { resolve(await task()) } catch (err) { reject(err) } } this.processing = false } }8. 常见问题排查
Uncaught (in promise)错误
- 忘记添加catch处理
- 在async函数中未使用try-catch
Promise不会自动执行
- 确认executor是同步调用resolve/reject
- 检查是否有未处理的异常
内存泄漏
- 长期挂起的Promise会保持引用
- 使用AbortController取消不需要的请求
执行顺序不符合预期
- 微任务与宏任务的区分
- then回调的注册时机影响执行顺序
9. 性能对比与优化
通过Benchmark.js测试我们的实现与原生Promise的性能差异:
// 我们的实现 const ourPromise = new MyPromise(r => r(1)) // 原生Promise const nativePromise = new Promise(r => r(1)) suite .add('Our Promise', () => { ourPromise.then(v => v + 1) }) .add('Native Promise', () => { nativePromise.then(v => v + 1) }) .on('cycle', event => console.log(String(event.target))) .run()优化建议:
- 减少不必要的Promise实例创建
- 避免深层Promise链
- 对高频操作使用记忆化缓存
10. 扩展思考
如何实现Promise取消?
- 使用AbortController
- 添加cancel令牌机制
与async/await的配合
- async函数本质是Promise语法糖
- 理解await的暂停恢复机制
在Node.js中的特殊处理
- process.nextTick与微任务
- 错误处理的最佳实践
前端框架中的集成
- React Suspense的Promise处理
- Vue的异步组件加载
手写Promise全家桶的过程让我对JavaScript异步编程有了更深的理解。在实际项目中,我建议至少实现一次基础版Promise,这会让你在调试复杂异步流程时事半功倍。当遇到棘手的异步问题时,不妨回到Promise的基本原理思考,往往能找到解决方案。