news 2026/8/12 13:35:27

深入理解Promise:从原理到手写实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
深入理解Promise:从原理到手写实现

1. 为什么我们需要手写Promise

2009年,当Node.js首次将JavaScript带入服务端开发领域时,回调地狱(Callback Hell)问题就成为了每个JS开发者心中的痛。层层嵌套的回调函数不仅让代码难以阅读,更给错误处理和流程控制带来了巨大挑战。直到ES6正式推出Promise规范,这种局面才得到根本性改变。

我在2016年接手一个遗留项目时,曾见过这样的代码:

fs.readFile('config.json', function(err, data) { if (err) return console.error(err); parseXML(data, function(err, result) { if (err) return console.error(err); db.query('SELECT...', function(err, rows) { if (err) return console.error(err); // 更多嵌套... }); }); });

这段代码暴露了回调模式的三个致命缺陷:

  1. 错误处理重复且分散
  2. 执行流程难以追踪
  3. 异步操作无法并行管理

2. Promise核心机制解析

2.1 状态机的本质

Promise本质上是一个状态机,包含三个不可逆的状态转换:

  • pending → fulfilled(通过resolve)
  • pending → rejected(通过reject)

我曾用简单的对象模拟过这个机制:

const fakePromise = { state: 'pending', value: null, reason: null, resolve: function(value) { if (this.state !== 'pending') return; this.state = 'fulfilled'; this.value = value; }, reject: function(reason) { if (this.state !== 'pending') return; this.state = 'rejected'; this.reason = reason; } }

2.2 微任务的奥秘

Promise的回调执行时机是个容易误解的点。在Chrome中实测以下代码:

console.log('script start'); setTimeout(() => { console.log('setTimeout'); }, 0); Promise.resolve().then(() => { console.log('promise1'); }).then(() => { console.log('promise2'); }); console.log('script end');

输出顺序将是:

script start script end promise1 promise2 setTimeout

这是因为Promise回调属于微任务(microtask),而setTimeout是宏任务(macrotask)。事件循环会优先清空微任务队列。

3. 完整实现手写Promise

3.1 基础框架搭建

我们先构建最简化的Promise骨架:

class MyPromise { constructor(executor) { this.state = 'pending'; this.value = undefined; this.reason = undefined; const resolve = (value) => { if (this.state !== 'pending') return; this.state = 'fulfilled'; this.value = value; }; const reject = (reason) => { if (this.state !== 'pending') return; this.state = 'rejected'; this.reason = reason; }; try { executor(resolve, reject); } catch (err) { reject(err); } } }

这个基础版本已经可以处理同步操作:

const p = new MyPromise((resolve) => { resolve(42); });

3.2 then方法实现

then方法是Promise最复杂的部分,需要考虑:

  1. 状态未完成时的回调暂存
  2. 链式调用支持
  3. 值穿透处理

完整实现:

then(onFulfilled, onRejected) { // 值穿透处理 onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v; onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err }; const promise2 = new MyPromise((resolve, reject) => { const handle = () => { queueMicrotask(() => { try { const x = this.state === 'fulfilled' ? onFulfilled(this.value) : onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (err) { reject(err); } }); }; if (this.state !== 'pending') { handle(); } else { this.onFulfilledCallbacks.push(() => handle()); this.onRejectedCallbacks.push(() => handle()); } }); return promise2; }

3.3 Promise解决过程

这是符合Promises/A+规范的核心:

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

4. 高级功能实现

4.1 静态方法实现

Promise.resolve的polyfill:

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

Promise.reject的简单实现:

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

4.2 all/race方法

Promise.all的典型实现:

static all(promises) { return new MyPromise((resolve, reject) => { const results = []; let count = 0; const processResult = (i, val) => { results[i] = val; if (++count === promises.length) resolve(results); }; promises.forEach((p, i) => { MyPromise.resolve(p).then( val => processResult(i, val), reject ); }); }); }

Promise.race的实现更简单:

static race(promises) { return new MyPromise((resolve, reject) => { promises.forEach(p => { MyPromise.resolve(p).then(resolve, reject); }); }); }

5. 常见问题与调试技巧

5.1 异步测试陷阱

测试Promise时常见的一个错误是忘记测试是异步的:

// 错误示例 it('should resolve', () => { const p = new MyPromise(resolve => resolve(42)); p.then(val => { expect(val).toBe(42); // 这行可能不会执行 }); }); // 正确写法 it('should resolve', done => { const p = new MyPromise(resolve => resolve(42)); p.then(val => { expect(val).toBe(42); done(); }); });

5.2 内存泄漏排查

在实现过程中,我曾遇到内存泄漏问题。通过Chrome DevTools的Memory面板发现,未完成的Promise会保持对回调函数的引用。解决方案是:

// 在适当的时候清理回调数组 this.onFulfilledCallbacks = null; this.onRejectedCallbacks = null;

5.3 性能优化点

  1. 使用微任务队列替代setTimeout:
// 优先使用现代API if (typeof queueMicrotask === 'function') { queueMicrotask(fn); } else { Promise.resolve().then(fn); }
  1. 避免不必要的Promise封装:
// 不推荐 new Promise(resolve => resolve(someValue)); // 推荐 Promise.resolve(someValue);

6. 从实现中学到的经验

在完整实现Promise的过程中,最深刻的体会是错误处理的重要性。最初版本我忽略了这两个关键点:

  1. executor中的同步错误必须捕获:
try { executor(resolve, reject); } catch (err) { reject(err); }
  1. then方法中的回调执行也需要try-catch:
try { const x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch (err) { reject(err); }

另一个收获是对异步编程本质的理解。通过实现Promise,我真正明白了:

  • 事件循环中微任务的优先级
  • 状态不可变性的重要性
  • 链式调用的实现原理

这些知识在调试复杂异步流程时非常有用。比如当遇到"Promise似乎没有执行"的情况时,现在我会首先检查:

  1. 是否忘记return新的Promise
  2. 是否有未捕获的异常
  3. 微任务队列是否被阻塞
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/12 13:31:21

5步掌握AudioSR:AI音频超分辨率让低质量音频焕然一新

5步掌握AudioSR:AI音频超分辨率让低质量音频焕然一新 【免费下载链接】versatile_audio_super_resolution Versatile audio super resolution (any -> 48kHz) with AudioSR. 项目地址: https://gitcode.com/gh_mirrors/ve/versatile_audio_super_resolution …

作者头像 李华
网站建设 2026/8/12 13:30:48

Vision Transformer补丁多样化策略:提升模型泛化能力与数据效率

1. 项目概述:为什么我们需要“多样化”的视觉补丁? 如果你最近在跟进计算机视觉领域的前沿进展,Vision Transformer(ViT)这个名字你一定不陌生。自从它将自然语言处理领域的Transformer架构成功“跨界”应用到图像识别…

作者头像 李华
网站建设 2026/8/12 13:30:05

Fan Control完全指南:Windows风扇控制软件从入门到精通

Fan Control完全指南:Windows风扇控制软件从入门到精通 【免费下载链接】FanControl.Releases This is the release repository for Fan Control, a highly customizable fan controlling software for Windows. 项目地址: https://gitcode.com/GitHub_Trending/f…

作者头像 李华
网站建设 2026/8/12 13:29:53

数据中心能耗危机:AI算力需求与绿色能源的博弈

你有没有想过,当你在电商平台下单、用语音助手查询天气、或者观看一部高清流媒体电影时,这些看似轻盈的“云端”服务,其物理心脏正以惊人的速度消耗着电力,并释放出巨量的温室气体?最近,一则关于亚马逊在得…

作者头像 李华
网站建设 2026/8/12 13:27:01

Linux内核下HYM8563 RTC芯片驱动移植实战指南

1. 项目缘起与RTC芯片选型考量 最近在为一个基于国产主控的嵌入式Linux项目做硬件功能扩展,需要增加一个实时时钟(RTC)模块。项目对功耗和成本有比较严格的要求,主控芯片自带的RTC在深度休眠模式下功耗不够理想,且需要…

作者头像 李华
网站建设 2026/8/12 13:26:47

Netty DelimiterBasedFrameDecoder:分隔符拆帧原理与实战避坑指南

1. 从“半包粘包”说起:为什么我们需要拆帧神器?如果你写过网络通信程序,尤其是基于TCP的,大概率听过“半包”和“粘包”这两个词。这不是TCP协议本身的缺陷,而是其“流式”特性带来的必然结果。简单来说,T…

作者头像 李华