手写Promise核心实现与面试要点解析

发布时间:2026/8/24 23:10:05
手写Promise核心实现与面试要点解析 1. Promise 手写实现的核心价值前端开发者在面试中经常会被要求手写Promise实现这已经成为检验JavaScript异步编程能力的试金石。我见过太多候选人在这个问题上栽跟头不是因为不会用Promise而是对它的内部机制理解不够透彻。手写Promise的难点不在于实现基本功能而在于要完整复现其行为特性。比如微任务队列的处理、链式调用的实现、错误冒泡机制等。很多人在实现then方法时就卡住了更不用说还要处理静态方法。2. Promise 基础结构实现2.1 构造函数与状态管理我们先从最基础的Promise构造函数开始。一个Promise有三种状态pending、fulfilled和rejected。这是整个实现的基石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) } } }这里有几个关键点需要注意状态一旦改变就不能再变从pending到fulfilled/rejected使用队列存储回调函数是为了支持异步resolve的情况executor执行过程中抛出错误应该直接reject2.2 then方法的实现then方法是Promise的核心也是面试中最常被考察的部分。它需要处理以下几种情况then(onFulfilled, onRejected) { // 参数校验 onFulfilled typeof onFulfilled function ? onFulfilled : value value 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模拟微任务队列处理onFulfilled/onRejected不是函数的情况实现链式调用需要返回新的Promise需要处理回调函数中抛出异常的情况2.3 resolvePromise函数这是Promise A规范要求的辅助函数用于处理thenable对象和循环引用function resolvePromise(promise2, x, resolve, reject) { if (promise2 x) { return reject(new TypeError(Chaining cycle detected for promise)) } if (x instanceof MyPromise) { x.then(resolve, reject) } else if (x ! null (typeof x object || typeof x function)) { let then try { then x.then } catch (e) { return reject(e) } 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 (e) { if (called) return reject(e) } } else { resolve(x) } } else { resolve(x) } }3. 静态方法实现3.1 Promise.resolve这个静态方法创建一个立即resolve的Promisestatic resolve(value) { if (value instanceof MyPromise) { return value } return new MyPromise(resolve { resolve(value) }) }需要注意处理参数本身就是Promise的情况。3.2 Promise.reject与resolve类似创建一个立即reject的Promisestatic reject(reason) { return new MyPromise((_, reject) { reject(reason) }) }3.3 Promise.all这是最复杂的静态方法之一需要处理多个Promise并行执行static all(promises) { return new MyPromise((resolve, reject) { if (!Array.isArray(promises)) { return reject(new TypeError(Argument must be an array)) } 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 ) }) if (promises.length 0) { resolve(results) } }) }关键点输入参数校验处理空数组情况保持结果顺序与输入一致任何一个Promise reject就立即reject3.4 Promise.race返回第一个settled的Promise结果static race(promises) { return new MyPromise((resolve, reject) { if (!Array.isArray(promises)) { return reject(new TypeError(Argument must be an array)) } promises.forEach(promise { MyPromise.resolve(promise).then(resolve, reject) }) }) }4. 常见问题与调试技巧4.1 微任务队列的实现原生Promise使用微任务队列而我们用setTimeout模拟的是宏任务。这在大多数面试场景下是可以接受的但要知道两者的区别微任务MutationObserver、process.nextTick宏任务setTimeout、setInterval4.2 循环引用检测这是很多手写实现会忽略的点const p new MyPromise(resolve { resolve(p) // 应该抛出TypeError })4.3 thenable对象处理Promise A规范要求处理thenable对象const thenable { then: function(resolve) { resolve(42) } } MyPromise.resolve(thenable).then(console.log) // 应该输出424.4 错误处理边界确保所有可能的错误都被捕获new MyPromise(() { throw new Error(test) }).catch(console.log) // 应该捕获错误5. 测试用例验证完整的Promise实现需要通过Promise A测试套件。我们可以写一些基本测试// 基本功能测试 new MyPromise(resolve resolve(1)) .then(val val 1) .then(console.log) // 2 // 异步测试 new MyPromise(resolve setTimeout(() resolve(2), 100)) .then(val val * 2) .then(console.log) // 4 // 静态方法测试 MyPromise.all([ MyPromise.resolve(1), new MyPromise(resolve setTimeout(() resolve(2), 100)), 3 ]).then(console.log) // [1, 2, 3] // 错误处理测试 new MyPromise((_, reject) reject(error)) .catch(err { throw new Error(err) }) .catch(console.log) // Error: error6. 性能优化考虑虽然面试中不常要求但实际实现时可以考虑减少不必要的闭包创建优化回调队列的数据结构避免重复的状态检查使用真正的微任务队列替代setTimeout7. 与其他异步方案的对比理解Promise与以下方案的异同Callback解决了回调地狱但仍有then链的问题async/await语法糖底层还是PromiseGenerator更底层的控制流管理RxJS响应式编程处理更复杂的异步场景8. 面试中的高频问题准备这些问题能帮你在面试中游刃有余Promise的三种状态及其转换条件then方法返回的是什么如何取消一个PromisePromise.all和Promise.race的区别如何实现Promise超时控制Promise的错误冒泡机制是怎样的为什么说Promise不能真正取消