news 2026/8/24 23:09:24

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
手写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) } } }

这里有几个关键点需要注意:

  1. 状态一旦改变就不能再变(从pending到fulfilled/rejected)
  2. 使用队列存储回调函数是为了支持异步resolve的情况
  3. executor执行过程中抛出错误应该直接reject

2.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 }

这里有几个关键实现细节:

  1. 使用setTimeout模拟微任务队列
  2. 处理onFulfilled/onRejected不是函数的情况
  3. 实现链式调用需要返回新的Promise
  4. 需要处理回调函数中抛出异常的情况

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的Promise:

static resolve(value) { if (value instanceof MyPromise) { return value } return new MyPromise(resolve => { resolve(value) }) }

需要注意处理参数本身就是Promise的情况。

3.2 Promise.reject

与resolve类似,创建一个立即reject的Promise:

static 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) } }) }

关键点:

  1. 输入参数校验
  2. 处理空数组情况
  3. 保持结果顺序与输入一致
  4. 任何一个Promise reject就立即reject

3.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、setInterval

4.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) // 应该输出42

4.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: error

6. 性能优化考虑

虽然面试中不常要求,但实际实现时可以考虑:

  1. 减少不必要的闭包创建
  2. 优化回调队列的数据结构
  3. 避免重复的状态检查
  4. 使用真正的微任务队列替代setTimeout

7. 与其他异步方案的对比

理解Promise与以下方案的异同:

  1. Callback:解决了回调地狱,但仍有then链的问题
  2. async/await:语法糖,底层还是Promise
  3. Generator:更底层的控制流管理
  4. RxJS:响应式编程,处理更复杂的异步场景

8. 面试中的高频问题

准备这些问题能帮你在面试中游刃有余:

  1. Promise的三种状态及其转换条件?
  2. then方法返回的是什么?
  3. 如何取消一个Promise?
  4. Promise.all和Promise.race的区别?
  5. 如何实现Promise超时控制?
  6. Promise的错误冒泡机制是怎样的?
  7. 为什么说Promise不能真正取消?
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/24 23:09:16

3D立体花朵制作指南:从设计到组装的完整手工教程

1. 项目概述与核心价值 最近在琢磨送什么礼物给女朋友,既不想落入俗套,又想表达点心意,最后决定自己动手做一个3D立体花朵。这玩意儿听起来有点手工活的意思,但其实它融合了设计、材料、工艺和一点点的耐心,成品的效果…

作者头像 李华
网站建设 2026/8/24 23:04:17

从黑盒子到透明管道:DigRF接口在移动通信中的演进与实战解析

1. 从“黑盒子”到“透明管道”:为什么我们需要DigRF在移动通信设备,特别是手机基带芯片和射频前端模块的设计与调试中,工程师们常常面临一个核心挑战:如何高效、可靠地传输高速、复杂的数字基带信号到射频收发器?在早…

作者头像 李华
网站建设 2026/8/24 23:01:58

C++ switch语句:从基础语法到底层实现与避坑指南

1. 从“if-else地狱”到“switch救赎”:为什么我们需要它?干了这么多年C,我见过太多新手写的代码,一长串的if-else if-else,像一堵密不透风的墙,读起来费劲,改起来更费劲。尤其是当你要根据一个…

作者头像 李华