news 2026/11/3 14:02:33

深入理解JavaScript Promise实现原理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
深入理解JavaScript Promise实现原理

1. 为什么需要手写Promise

在JavaScript的世界里,异步编程一直是个绕不开的话题。记得我刚入行时,面对层层嵌套的回调函数(callback hell),代码缩进都快超出屏幕右边距了。直到ES6引入了Promise,才让异步代码有了线性表达的可能。

但仅仅会使用Promise还远远不够。去年面试一个三年经验的候选人时,我让他解释Promise的then方法如何实现链式调用,对方支支吾吾说不出所以然。这正是促使我深入研究Promise实现原理的契机——只有亲手实现过,才能真正理解这个每天都要打交道的工具。

2. Promise的核心机制剖析

2.1 状态机的本质

Promise本质上是一个状态机,它有三种状态:

  • PENDING(等待)
  • FULFILLED(成功)
  • REJECTED(失败)

状态一旦改变就不可逆。这个特性用代码表示就是:

class MyPromise { constructor(executor) { this.state = 'PENDING' this.value = undefined this.reason = undefined const resolve = (value) => { if (this.state === 'PENDING') { this.state = 'FULFILLED' this.value = value } } const reject = (reason) => { if (this.state === 'PENDING') { this.state = 'REJECTED' this.reason = reason } } try { executor(resolve, reject) } catch (err) { reject(err) } } }

2.2 微任务队列的玄机

Promise的回调属于微任务(microtask),这决定了它的执行时机。在浏览器中我们常用queueMicrotask或MutationObserver模拟,Node环境下可以用process.nextTick:

const asyncTask = (fn) => { if (typeof queueMicrotask === 'function') { queueMicrotask(fn) } else if (typeof MutationObserver !== 'undefined') { const observer = new MutationObserver(fn) const textNode = document.createTextNode('') observer.observe(textNode, { characterData: true }) textNode.data = '1' } else { setTimeout(fn, 0) } }

注意:虽然setTimeout也能实现异步,但它属于宏任务(macrotask),与原生Promise的行为有差异,只应作为降级方案。

3. Then方法的实现艺术

3.1 基础链式调用

then方法需要返回新的Promise,这是链式调用的关键:

then(onFulfilled, onRejected) { const promise2 = new MyPromise((resolve, reject) => { if (this.state === 'FULFILLED') { asyncTask(() => { try { const x = onFulfilled(this.value) resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e) } }) } // 其他状态处理... }) return promise2 }

3.2 值穿透的陷阱

当then的参数不是函数时,要实现"值穿透"特性:

then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err } // ...其余代码不变 }

4. 解决程序(resolvePromise)的复杂逻辑

这是整个实现中最复杂的部分,需要处理各种边界情况:

function resolvePromise(promise2, x, resolve, reject) { // 防止循环引用 if (promise2 === x) { return reject(new TypeError('Chaining cycle detected')) } // 处理thenable对象 if ((typeof x === 'object' && x !== null) || 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) } }

5. 其他原型方法的实现

5.1 catch方法

catch实际上是then的语法糖:

catch(onRejected) { return this.then(null, onRejected) }

5.2 finally方法

finally的特点是不影响Promise链的值传递:

finally(callback) { return this.then( value => MyPromise.resolve(callback()).then(() => value), reason => MyPromise.resolve(callback()).then(() => { throw reason }) ) }

6. 静态方法的实现

6.1 Promise.resolve

需要处理thenable对象的递归解析:

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

6.2 Promise.reject

与resolve不同,reject不会展开thenable对象:

static reject(reason) { return new MyPromise((_, reject) => { reject(reason) }) }

6.3 Promise.all

需要处理空数组等边界情况:

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 if (promises.length === 0) { return resolve(results) } promises.forEach((p, i) => { MyPromise.resolve(p).then( value => { results[i] = value if (++count === promises.length) { resolve(results) } }, reject ) }) }) }

7. 测试你的Promise实现

7.1 使用Promises/A+测试套件

安装测试工具:

npm install promises-aplus-tests -D

添加适配器:

MyPromise.deferred = function() { const dfd = {} dfd.promise = new MyPromise((resolve, reject) => { dfd.resolve = resolve dfd.reject = reject }) return dfd }

运行测试:

npx promises-aplus-tests your-promise.js

7.2 常见边界测试用例

// 1. 同步resolve new MyPromise(resolve => resolve(1)).then(console.log) // 2. 异步resolve new MyPromise(resolve => { setTimeout(() => resolve(2), 100) }).then(console.log) // 3. thenable对象 new MyPromise(resolve => { resolve({ then(resolve) { resolve(3) } }) }).then(console.log) // 4. 链式调用 MyPromise.resolve(4) .then(v => v * 2) .then(console.log)

8. 性能优化实践

8.1 避免不必要的微任务

在状态已确定时,可以优化then方法的执行:

then(onFulfilled, onRejected) { // 状态已确定时直接执行,不进入微任务队列 if (this.state === 'FULFILLED') { const promise2 = new MyPromise((resolve, reject) => { try { const x = onFulfilled(this.value) resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e) } }) return promise2 } // ...其他状态处理 }

8.2 内存泄漏防范

在大型应用中要注意:

  • 及时处理被拒绝的Promise(避免未捕获的rejection)
  • 避免在Promise闭包中保存大对象
  • 对长时间pending的Promise设置超时:
function timeoutPromise(promise, ms) { return MyPromise.race([ promise, new MyPromise((_, reject) => { setTimeout(() => reject(new Error('timeout')), ms) }) ]) }

手写Promise的过程就像在解构一个精密的瑞士手表,每个齿轮的咬合都需要精确到位。当最后通过所有测试用例时,那种对异步编程豁然开朗的感觉,比任何文档教程都来得深刻。建议每个JS开发者都至少完整实现一次Promise,这会是理解异步编程最好的投资。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/3 14:00:54

dsh-workbuddy-connect安装避坑:依赖、配置与排错

dsh-workbuddy-connect 这个连接组件,十个第一次装的人里有六七个会卡在同一个地方:包解压了、命令也跑起来了,日志最后一行停在handshake pending不动,人立刻开始怀疑网络,其实八成是本地环境漏了一项或者配置里少了个…

作者头像 李华
网站建设 2026/11/3 13:58:28

Sapera++工业相机采集入门:三步跑通图像采集流程

简介:本资源是一份面向图像处理初学者的Dalsa Sapera开发入门指南,聚焦C环境下Sapera库的核心使用流程,解决新手在工业相机图像采集、缓冲区管理与基础GUI集成中常见的环境配置与类调用问题。文档系统梳理了Sapera两大类——基础应用类&#…

作者头像 李华
网站建设 2026/11/3 13:57:03

Paperclip:面向前端工程师的轻量级AI Agent运行时框架

1. 这不是回形针,是AI时代前端工程师的“新工作台” 你搜“paperclip”,第一反应可能是办公桌抽屉里那个银色小金属片——但今天我要聊的,是GitHub上星标破万、被掘金和V2EX高频讨论、在ReactNode.js开发者圈子里悄悄火起来的开源项目 Paper…

作者头像 李华
网站建设 2026/11/3 13:55:58

Hadoop3伪分布式环境搭建与配置指南

1. Hadoop3伪分布式环境搭建全指南 作为大数据生态的核心组件,Hadoop的伪分布式模式是每个开发者必须掌握的实验环境。不同于单机模式仅用于功能验证,伪分布式完整模拟了Hadoop集群的各个组件,包括NameNode、DataNode和ResourceManager等守护…

作者头像 李华
网站建设 2026/11/3 13:55:39

AB1568头戴耳机开发实战:ANC切换、NVkey固化与UI Shell事件框架

简介:这份PDF资料面向从事Airoha洛达AB1568蓝牙耳机固件与应用开发的工程师,系统梳理该芯片平台从底层启动到上层功能实现的完整知识脉络,帮助开发者快速建立工程认知、定位关键代码入口。内容围绕headset工程展开,涵盖上电后执行…

作者头像 李华
网站建设 2026/11/3 13:54:30

硬件测试入门指南:从检视到可靠性,建立完整测试闭环

简介:这是一份面向测试工程师入门与进阶的硬件测试技术培训教材,以PPT形式系统梳理硬件测试的理念、目的、方法与流程,适合刚接触硬件测试的工程师、研发转测试人员及需要建立测试规范的技术管理者参考。压缩包内共1个PPT文件,约7…

作者头像 李华