手写Promise:深入解析JavaScript Promise原理
Promise 是 JavaScript 中进行异步编程的解决方案之一,它代表了一个异步操作的最终完成(或失败)及其结果值。它提供了一种更结构化、更易于管理和理解异步操作的方式,旨在解决传统回调函数所导致的“回调地狱 (Callback Hell)”问题。Promise 对象在 ES6 (ECMAScript 2015) 中被正式纳入标准,成为了现代 JavaScript 异步编程的基石。
核心思想:Promise 是一种异步操作的状态容器。它将异步操作封装成一个对象,提供统一的接口(.then(), .catch(), .finally())来处理操作成功时的数据和失败时的错误,从而避免深层嵌套回调,使异步代码扁平化且更具可读性。
一、为什么需要 Promise?—— 回调地狱的困境
在 Promise 出现之前,JavaScript 主要通过回调函数 (Callback Function) 来处理异步操作。当多个异步操作需要按顺序执行,或者一个异步操作的结果依赖于另一个异步操作时,就会导致回调函数的层层嵌套,形成难以阅读和维护的“回调地狱”:
1 | // 传统回调地狱示例 |
这种代码不仅可读性差,错误处理也十分复杂。Promise 正是为了解决这些痛点而生。
二、Promise 的基本概念
2.1 Promise 的三种状态
Promise 对象有且仅有以下三种状态:
- Pending (进行中):初始状态,既不是成功也不是失败。
- Fulfilled (已成功):操作成功完成,Promise 成功地返回了一个值。
- Rejected (已失败):操作失败,Promise 抛出了一个错误原因。
状态转换规则:
- Promise 只能从
Pending状态转换为Fulfilled或Rejected状态。 - 一旦 Promise 转换到
Fulfilled或Rejected状态,其状态就不可再改变,且会携带一个最终结果(成功值或失败原因)。这个过程被称为 settled (已敲定)。
graph TD
A[Pending] --> B{Fulfilled}
A[Pending] --> C{Rejected}
B --> D[Settled]
C --> D[Settled]
2.2 Promise 的构造函数
Promise 的构造函数接收一个 executor (执行器) 函数作为参数。这个 executor 函数会立即执行,并接收两个参数:resolve 和 reject,它们都是函数。
resolve(value):在异步操作成功时调用,将 Promise 的状态从Pending变为Fulfilled,并将value作为成功的结果。reject(reason):在异步操作失败时调用,将 Promise 的状态从Pending变为Rejected,并将reason作为失败的原因。
1 | const myPromise = new Promise((resolve, reject) => { |
2.3 then(), catch(), finally()
Promise 对象提供了 .then(), .catch(), .finally() 方法来注册在 Promise 状态改变时执行的回调函数。
.then(onFulfilled, onRejected):- 接收两个可选参数:
onFulfilled(成功回调) 和onRejected(失败回调)。 onFulfilled在 Promise 状态为Fulfilled时执行,接收成功值作为参数。onRejected在 Promise 状态为Rejected时执行,接收失败原因作为参数。then()方法总是返回一个新的 Promise,允许进行链式调用 (Chaining)。
- 接收两个可选参数:
.catch(onRejected):- 是
.then(null, onRejected)的语法糖,专门用于处理 Promise 链中的错误。 - 如果在
then的onFulfilled回调中抛出错误,或者上一个 Promise 被rejected,.catch()会捕获到这个错误。
- 是
.finally(onFinally):- ES2018 引入。无论 Promise 最终是
Fulfilled还是Rejected,finally()回调都会执行。 - 它不接收任何参数,且返回值不会影响 Promise 链的最终结果(但如果
finally回调返回一个 rejected Promise,则会覆盖之前的状态)。 - 常用于执行清理工作。
- ES2018 引入。无论 Promise 最终是
链式调用示例:
1 | myPromise |
2.4 错误冒泡机制
Promise 的错误处理具有冒泡机制:
- 当一个 Promise 被
rejected,或者在任何.then()或.catch()回调中抛出同步错误,这个错误会沿着 Promise 链向下传递,直到遇到最近的.catch()或.then(null, onRejected)回调来处理它。 - 如果没有
catch处理器来处理 Promise 链中的错误,该错误最终会作为未捕获的 Promise 拒绝 (unhandled promise rejection) 冒泡到全局,可能导致应用程序崩溃或警告。
三、Promise 的静态方法
除了实例方法,Promise 构造函数还提供了一些有用的静态方法来处理多个 Promise。
Promise.all(iterable):- 接收一个 Promise 可迭代对象(如数组)作为参数。
- 当所有 Promise 都
Fulfilled时,Promise.all返回一个Fulfilled的 Promise,其结果是一个数组,包含所有 Promise 的成功值(按传入顺序)。 - 只要其中任何一个 Promise 被
Rejected,Promise.all就会立即Rejected,并返回第一个拒绝的原因。 - 适用场景:所有异步任务都需要成功。
Promise.race(iterable):- 接收一个 Promise 可迭代对象作为参数。
- 返回一个 Promise,其状态和结果与可迭代对象中第一个
settled的 Promise 相同(无论是Fulfilled还是Rejected)。 - 适用场景:赛跑,只关心最快的那个结果。
Promise.allSettled(iterable)(ES2020):- 接收一个 Promise 可迭代对象作为参数。
- 返回一个 Promise,当所有 Promise 都已敲定 (settled,即无论是
Fulfilled还是Rejected) 时,该 Promise 会Fulfilled。 - 其结果是一个对象数组,每个对象描述了每个 Promise 的结果,包含
status('fulfilled'或'rejected') 和value(成功值) 或reason(失败原因)。 - 适用场景:不关心所有任务是否成功,只关心所有任务是否都已完成。
Promise.any(iterable)(ES2021):- 接收一个 Promise 可迭代对象作为参数。
- 返回一个 Promise,当其中任何一个 Promise 被
Fulfilled时,该 Promise 就会Fulfilled,并返回第一个成功的结果。 - 如果所有 Promise 都被
Rejected,则Promise.any返回一个Rejected的 Promise,并带有一个AggregateError类型的错误,其中包含了所有拒绝的原因。 - 适用场景:只要有一个成功就足够。
1 | // Promise.all 示例 |
四、手写一个简易版 Promise
现在,我们尝试手写一个名为 MyPromise 的简易 Promise 实现,以加深理解其内部机制。这个实现将覆盖 Promise 的核心功能:状态管理、resolve/reject、then 方法的链式调用和错误处理。
核心思路:
- 状态管理:使用
this.state存储 Promise 当前状态 (pending,fulfilled,rejected)。 - 结果存储:使用
this.value存储成功结果,this.reason存储失败原因。 - 回调队列:因为
then可能在 Promise 状态为pending时被调用,需要存储所有待执行的onFulfilled和onRejected回调函数。当 Promise 状态改变时,遍历并执行这些回调。 - 异步执行:为模拟 Promise 的异步特性,我们将回调函数的执行放入
setTimeout(..., 0)中,使其成为宏任务 (Macrotask),尽管原生 Promise 使用微任务 (Microtask)。 - 链式调用:
then方法必须返回一个新的Promise对象。新 Promise 的状态由then中回调函数的返回值决定。
1 | /** |
测试手写 Promise:
1 | console.log('--- 开始测试 MyPromise ---'); |
代码解释:
constructor(executor):接收执行器函数,并立即执行。它定义了resolve和reject局部函数,供executor调用。- 状态和回调队列:
state,value,reason存储 Promise 的核心信息。onFulfilledCallbacks和onRejectedCallbacks是关键,用于在 Promise 处于pending状态时,暂存.then()方法注册的回调。 resolve(value)和reject(reason):它们会检查当前状态,只在pending时才会修改状态和结果。状态一旦改变,它们就会遍历并异步执行所有相应的回调(setTimeout(..., 0)模拟异步)。then(onFulfilled, onRejected):- 首先对
onFulfilled和onRejected进行类型检查,并提供默认的“透传”行为,以确保链式调用能够继续。 - 核心是返回一个新的
MyPromise对象 (promise2)。 handleCallback辅助函数处理了回调执行中的所有情况:setTimeout确保回调异步执行。- 捕获回调执行中的同步错误。
- 如果回调返回一个
MyPromise实例,则等待该 Promise 解决,并将结果传递给promise2。 - 如果回调返回非 Promise 值,则直接用该值解决
promise2。
- 根据当前
MyPromise的状态,then会立即执行相应的回调,或者将它们推入onFulfilledCallbacks/onRejectedCallbacks队列。
- 首先对
catch(onRejected):直接复用then(null, onRejected)。finally(callback):在then方法的基础上实现,确保callback总是执行,且其返回值不会改变 Promise 的最终状态(除非callback返回一个拒绝的 Promise)。- 静态方法
resolve和reject:提供快速创建已解决或已拒绝 Promise 的方式。 - 静态方法
all:接收 Promise 数组,并返回一个新的 Promise。当所有传入的 Promise 都成功时,它才成功;否则,只要有一个失败,它就失败。
五、总结
Promise 是 JavaScript 异步编程的核心,它通过将异步操作包装成一个状态可控的对象,并提供链式调用的 .then(), .catch(), .finally() 方法,彻底改变了传统回调地狱的困境。理解 Promise 的状态、状态转换规则以及链式调用的机制,对于编写清晰、健壮的异步 JavaScript 代码至关重要。通过手写一个简易 Promise,我们不仅深入理解了其内部工作原理,也为掌握更高级的异步特性(如 async/await)打下了坚实的基础。
