Promise的原理与实现
# Promise的原理与实现
Promise
是异步编程的一种解决方案,它比传统的解决方案“回调函数和事件”更合理和更强大。Promise
由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise
对象。
一个Promise
是一个代理,它代表一个在创建promise时不一定已知的值。它允许你将处理程序与异步操作的最终成功值或失败原因关联起来。这使得异步方法可以像同步方法一样返回值:异步方法不会立即返回最终值,而是返回一个promise,以便在将来的某个时间点提供该值。
# 一、Promise的特点
# 状态与状态流转
Promise
有三种状态:pending
、fulfilled
、rejected
。pending
:初始状态,既没有被兑现,也没有被拒绝。fulfilled
:意味着操作成功完成。rejected
:意味着操作失败。
Promise
只能从pending
状态变为fulfilled
或rejected
状态。Promise
的状态一旦改变,就不会再变。
# 链式调用
连续执行两个或者多个异步操作是一个常见的需求,在上一个操作执行成功之后,开始下一个的操作,并带着上一步操作所返回的结果。在传统的回调风格中,这种操作会导致经典的回调地狱问题 (opens new window)。Promise
通过一个Promise链
来解决这个问题,这也是Promise API 的核心优势。
Promise
对象具有then
方法,then
方法接收两个参数:onFulfilled
和onRejected
。then
方法返回一个新的Promise
对象,所以支持链式调用。
# ES6中Promise的组成部分
在ES6的标准中,Promise
包含以下几个主要的部分:
Promise
构造函数Promise
静态方法和静态属性Promise
实例方法
其中构造函数与实例方法中的then
方法是Promise规范的组成部分,也是最主要的的部分,其它的静态方法和静态属性等是Promise
的辅助工具。
# 二、Promise的基本使用
const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 1000)
}).then(res => {
return 1
}).catch(err => {
console.log('错误1', err)
})
p.then(res => {
console.log(res)
return 2
})
p.then(res => {
console.log(res)
throw new Error('error')
}).catch(err => {
console.log("错误2", err)
})
// 输出:
"succes"
1
2
"错误2 Error: error"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 三、Promise的规范
我们现在谈的Promise规范
通常指Promise/A+规范 (opens new window)。主要内容如下:
# 1. 相关术语
Promise
:是一个具有then
方法的符合本规范的对象或函数thenable
:是一个具有then
方法的对象或函数value
:一个合法的Javascript值exception
:通过throw
声明的一个异常值reason
:一个合法的Javascript值,用于拒绝Promise
的原因
# 2. 要求
规范对Promise
的状态、then
方法的要求以及Promise
的解析过程都做了要求,这也是Promise
规范中最核心的部分。
# 2.1 Promise的状态
一个promise的状态必须是pending
,fulfilled
和 rejected
中的一个。
- 当处于
pending
状态时,可以改变状态为fulfilled
,或者改变状态为rejected
。 - 当处于
fulfilled
状态时,不能改变为任何状态,且必须有个不可被改变的value
。 - 当处于
rejected
状态时,不能改变为任何状态,且必须有个不可被改变的reason
。
提示
此处说的“不可改变”,只是其引用不可变,但如果值本身是个对象,它的内容是可能被改变的。即不保证深度不可变。
# 2.2 then方法
一个promise必须提供一个then
方法,用于处理其value
和reason
。该方法接受两个参数:onFulfilled
和onRejected
。
对于promise.then(onFulfilled, onRejected)
:
onFulfilled
和onRejected
都是可选参数。- 如果
onFulfilled
不是一个函数,则onFulfilled
必须被忽略。 - 如果
onRejected
不是一个函数,则onRejected
必须被忽略。
- 如果
- 如果
onFulfilled
是一个函数,则:- 它必须在
promise
状态变为fulfilled
时调用,并接收value
作为第一个参数。 - 在变为
fulfilled
之前不能被调用 - 只能被调用一次
- 它必须在
- 如果
onRejected
是一个函数,则:- 它必须在
promise
状态变为rejected
时调用,并接收reason
作为第一个参数。 - 在变为
rejected
之前不能被调用 - 只能被调用一次
- 它必须在
- 在执行上下文堆栈(
execution contetxt stack
)只包含平台代码(platform code
)之前,不能调用onFulfilled
或onRejected
。 onFulfilled
或onRejected
必须被作为普通函数调用(比如没有this
值)。then
可以在同一个promise上多次调用- 当promise已经处于
fulfilled
或变为fulfilled
状态时,所有的onFulfilled
回调函数都会按顺序被调用。 - 当promise已经处于
rejected
或变为rejected
状态时,所有的onRejected
回调函数都会按顺序被调用。
- 当promise已经处于
then
必须返回一个promise:promise2 = promise1.then(onFulfilled, onRejected)
- 如果
onFulfilled
或者onRejected
返回一个值x
,则进入Promise的解析流程,调用[[Resolve]](promise2, x)
- 如果
onFulfilled
或者onRejected
抛出一个异常e
,则promise2
必须被拒绝,并将e
作为拒绝原因 - 如果
onFulfilled
不是一个函数且promise1
状态变为fulfilled
,promise2
必须被设置成fulfilled
,并将promise1
的值作为成功原因 - 如果
onRejected
不是一个函数且promise1
状态变为rejected
,promise2
必须被设置成rejected
,并将promise1
的拒绝原因作为promise2
的拒绝原因
- 如果
# 2.3 Promise解析过程
Promise
解析时接收一个promise
和一个x
作为参数,根据x
的类型,执行不同的操作,并将这个过程记为[[Resolve]](promise, x)
。如果x
是一个Thenable
对象,那么会在假设x
的行为表现得像一个Promise
的基础上,让promise
采取x
的状态。否则就使用x
来兑现promise
。
这种对thenable
的处理使得Promise
的实现更具通用性:只要它们暴露了一个遵循**Promise/A+**规范的then
方法,那么就它们之间就可以相互操作。同时也使得Promise
的实现可以与那些不太符合规范的实现能够良好的共存。
运行[[Resolve]](promise2, x)
,需要经过以下步骤:
为了与前面的
then
方法呼应上,这里将promise
记为promise2
- 如果
promise2
和x
是同一个对象,则promise2
必须被拒绝,并抛出一个TypeError
作为拒绝原因。 - 如果
x
是一个Promise
,则promise2
采用x
的状态:- 如果
x
处于pending
状态,promise2
必须保持pending
状态,直到x
被兑现或拒绝。 - 如果
x
处于fulfilled
状态,则promise2
必须被兑现,并采用x
相同的终值。 - 如果
x
处于rejected
状态,则promise2
必须被拒绝,并采用相同的拒绝原因。
- 如果
- 否则,如果
x
是一个对象或者函数:- 令
then
等于x.then
- 如果
x.then
取值抛出了异常e
,则以e
作为原因拒绝promise2
- 如果
then
是一个函数,则将x
作为this
执行then
,并传入两个参数:第一个参数是resolvePromise
,第二个参数是rejectPromise
。 - 当
resolvePromise
接收一个参数y
被调用时,执行[[Resolve]](promise2, y)
- 当
rejectPromise
接收一个参数r
被调用时,以r
作为原因拒绝promise2
resolvePromise
和rejectPromise
只能被调用一次- 如果
then
执行抛出一个异常e
,promise2
以e
拒绝:如果resolvePromise
或rejectPromise
已经执行过了,则忽略这个异常,否则以e
拒绝promise2
- 否则,
then
不是一个函数,则promise2
以x
为值解决
- 令
- 否则,
promise2
以x
为值解决
注意
如果一个promise
被一个循环的thenable
链中的对象解决,而[[Resolve]](promise, x)
的递归性质又使得其被再次调用,根据上述的算法,这个promise
将陷入无限的递归当中并永远处于pending
状态。算法虽然不强制要求处理这种情况,但是鼓励实现者检测这样的递归是否存在,若检测到,则 抛出一个TypeError
拒绝这个promise
。
示例
let thenable = {
then: function(onFulfilled, onRejected) {
onFulfilled(thenable)
}
}
let promise = new Promise((resolve, reject) => {
resolve(thenable)
})
promise.then(
res => console.log(res),
err => console.log(err)
)
2
3
4
5
6
7
8
9
10
11
12
13
14
# 3. 规范解释
- 规范中所说的
“平台代码”
指的是引擎、环境以及Promise的实现代码。在实践中要保证onFulfilled
和onRejected
函数是异步执行的,并且是在then
方法被调用后的新一轮事件循环中的新执行栈中执行。这个机制可以采用“宏任务(Macro Task)”的机制来实现,比如setTimeout
或setImmediate
;也可以使用“微任务(Micro Task)”的机制来实现,如MutationObserver
或process.nextTick
。由于Promise的实现被认为是平台代码的一部分,它可能自身就包含了一个任务调度队列或者trampoline
(一种递归到尾调用的优化技术)。 onFulfilled
或onRejected
必须被作为普通函数调用,在严格模式中,其this为undefined,否则为全局对象- 在处理
thenable
时,令then=x.then
并测试该引用,这种方式可以避免多次访问x.then
,确保属性的一致性,因为其值可能在检索调用时发生了改变。
# 4. ECMCScript中的Promise规范
从以上的规范来看,Promise/A+ 规范并没有提及我们平时使用Promise
时使用到的构造函数、reject函数、resolve函数以及各种静态方法的具体定义和实现方式(但是指明了它们之间是同步还是异步执行的要求)。这是因为Promises/A+规范的思想并不是提供某种库API。它是为了提供一组最小的可互操作的原语,以便任何使用Promise
的人都可以依赖于它具有统一的接口。这对于跨库互操作尤其重要,例如在应用程序的不同组件的接缝处,可能在内部使用不同的Promise
库。为Promise
库指定完整的API并不是实现这一目标的必要条件。
然而ECMAScript6中,新增了Promise
对象是对Promise/A+规范的一种具体实现。它对Promise
实现的要求提出更加严格的要求,已经超出了Promise/A+
规范。换句话说,ECMAScript的Promise
对象只是众多``Promise/A+`规范实现的一种,你自己的实现则不一定非要严格遵循ECMAScript的Promise规范。
ECMAScript@2025 Language Specification#Promise-Object (opens new window)
以下为ECMAScript的Promise规范对reject函数
、resolve函数
以及constructor
函数的定义:
resolve(resolution)
:
- 令
F
为激活的函数对象 - 确保
F
是一个具有[[Promise]]
属性(内部槽)的对象 - 令
promise
为F
.[[Promise]]属性的值 - 令
alreadyResolved
为F.[[AlreadyResolved]]属性的值 - 如果
alreadyResolved
.[[Value]]为true,return undefined
- 令
alreadyResolved
.[[Value]]为true - 如果
resolution
为promise
,throw a TypeError
- 如果
resolution
为不是一个对象,则执行FulfillPromise(promise, resolution)
,并return undefined
- 否则令
then
为resolution.then
(处理thenable,处理步骤与上文promise.then
基本一致) return undefined
reject(reason)
:
- 令
F
为激活的函数对象 - 确保
F
是一个具有[[Promise]]
属性(内部槽)的对象 - 令
promise
为F
.[[Promise]]属性的值 - 令
alreadyResolved
为F.[[AlreadyResolved]]属性的值 - 如果
alreadyResolved
.[[Value]]为true,return undefined
- 令
alreadyResolved
.[[Value]]为true - 执行
RejectPromise(promise, reason)
return undefined
提示
ECMAScript中,reject
和resolve
都是内置的匿名函数,它们含有[[Promise]]
、[[AlreadyResolved]]
等属性,是Promise
对象的内部槽。
constructor(executor)
:
- 如果NewTarget是
undefined
,抛出一个TypeError
异常。 - 如果
executor
不可调用,抛出一个TypeError
异常。` - 创建
promise
对象,且原型为Promise.prototype
,定义[[PromiseState]]
、[[PromiseResult]]
、[[PromiseFulfillReactions]]
、[[PromiseRejectReactions]]
、[[PromiseIsHandled]]
。 - 设置
[[PromiseState]]
为pending
。 - 设置
[[PromiseFulfillReactions]]
为空数组。 - 设置
[[PromiseRejectReactions]]
为空数组。 - 设置
[[PromiseIsHandled]]
为false
。 - 创建
resolvingFunctions
,即用于改变状态的resolve
和reject
函数。 - 调用
executor
函数,并传入resolve
和reject
函数作为参数。结果为completion
- 如果
completion
是异常值,则调用reject
函数,并传入completion
作为参数。 - 返回
promise
。
# 四、Promise的实现
如上文所说Promise/A+
规范一种解决思想,不是具体的实现。在社区中有非常多的实现,除了Javascript的各种具体实现方式,还包括Java、Python、PHP、Swift、Objective-C等等。可以点击这里查看Conformant Implementations (opens new window)。
下面,我们将一步步的实现Promise/A+
规范以及ECMAScript Promise规范
中的一些静态方法。
# 1. 声明Promise类
class Promise {
// 构造函数
constructor(executor) {
function resolve(value) { } // resolve函数
function reject(reason) { } // reject函数
}
then(onFulfilled, onRejected) { } // then方法
catch(onRejected) { } // catch方法
finally(onFinally) { } // finally方法
static resolve(value) { } // resolve静态方法
static reject(reason) { } // reject静态方法
static all(promises) { } // all静态方法
static race(promises) { } // race静态方法
static any(promises) { } // any静态方法
static allSettled(promises) { } // allSettled静态方法
static withResolvers() { } // withResolvers静态方法
}
// Promise解析函数
function resolvePromise(promise, x, resolve, reject) {}
// 判断是否为函数的辅助函数
function isFunction(o) {
return typeof o === 'function'
}
// 异步任务函数的辅助函数
function runTask() {}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 2. 构造函数constructor(executor)
参照ECMAScript Promise
规范对构造函数的定义,实现构造函数还是相对比较简单的,代码如下:
constructor(executor) {
// 必须使用new调用
if(new.target !== Promise) {
throw new TypeError('Promise constructor cannot be invoked without new operator');
}
// executor必须为函数
if (typeof executor !== 'function') {
throw new TypeError(`Promise resolver ${executor} is not a function`)
}
this.state = 'pending' // 初始状态
this.value = undefined // 兑现终值
this.reason = undefined // 拒绝原因
this.onFulfilledCallbacks = [] // 存储成功回调
this.onRejectedCallbacks = [] // 存储失败回调
// resolve和reject函数
function resolve(value){}
function reject(reason){}
try {
// 同步执行executor
executor(resolve, reject)
} catch (error) {
// executor执行异常中断,promise需要被reject
reject(error)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 3. resolve和reject函数
以下
runTask
是一个执行异步任务的函数,在后文定义
// resolve函数
function resolve(value) {
// 如果value是promise,则需要等待promise执行完成,再执行then
if (value instanceof Promise) {
return value.then(resolve, reject)
}
// resolve回调必须是异步调用的
runTask(() => {
if (this.state !== 'pending') return // 状态只能改变一次
this.value = value // 设置终值
this.state = 'fulfilled' // 状态标记为fulfilled
// 遍历fulfilled回调队列,以当前终值为参数按顺序调用所有的兑现回调函数
this.onFulfilledCallbacks.forEach(fn => fn(this.value))
})
}
// reject函数
function reject(value) {
runTask(() => {
if (this.state !== 'pending') return // 状态只能改变一次
this.reason = reason // 设置拒绝原因
this.state = 'rejected' // 状态标记为rejected
// 遍历rejected回调队列,以当前拒因为参数按顺序调用所有的拒绝回调函数
this.onRejectedCallbacks.forEach(fn => {
fn(this.reason)
})
})
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
resolve
和reject
的核心实现是改变状态和保存兑现的值或者拒绝的原因,然后开启一个异步任务,将回调函数添加到回调队列中。同时应该保证,Promise
的状态只能被改变一次。
# 4. then实例方法
then
方法是整个Promise
的核心,只要then
方法实现了,Promise
上的各种常见的静态方法都可以基于它来实现。then
方法接收两个参数,第一个参数是onFulfilled
,表示兑现后要执行的回调函数,第二个参数是onRejected
,表示拒绝后要执行的回调函数。我们参照规范,逐步来实现then
方法。
then(onFulfilled, onRejected)
:
首先,then方法必须返回一个新的Promise
实例。
then() {
const newPromise = new Promise((resolve, reject) => {
})
return newPromise
}
2
3
4
5
6
其次,then方法必须接受两个参数,onFulfilled
和onRejected
。它们都是可选的,如果不传入函数则忽略
then(onFulfilled, onRejected) {
onFulfilled = isFunction(onFulfilled) ? onFulfilled : undefined
onRejected = isFunction(onRejected) ? onRejected : undefined
const newPromise = new Promise((resolve, reject) => {
})
return newPromise
}
2
3
4
5
6
7
8
9
接着就是onFulfilled
和 onRejected
的处理程序了。规范要求,如果promise
处于pending
状态,那么onFulfilled
和onRejected
必须被添加到promise
的onFulfilledCallbacks
和onRejectedCallbacks
数组中。同时同一个promise
的then
方法是可以被多次调用的,它们应该被按顺序添加到对应的数组中。如果promise
的状态已经改变,那么onFulfilled
或onRejected
应该被执行。因此,then
方法中的新Promise的执行主体应该包含三种情况来处理。
const newPromise = new Promise((resolve, reject) => {
if(this.state === 'pending') {
// 存储回调
return
}
if(this.state === 'fulfilled'){
// 执行兑现回调
return
}
if(this.state === 'rejected'){
// 执行拒绝回调
return
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
然后根据then
函数处理onFulfilled
和 onRejected
的以下几点要求,我们继续完善代码:
onFulfilled
和onRejected
是异步执行的,所以需要将onFulfilled
和onRejected
函数放入异步任务队列中执行。onFulfilled
和onRejected
必须被作为普通函数调用- 如果
onFulfilled
或onRejected
返回一个值x
时,调用resolvePromise
函数进行解析;否则如果它们抛出异常,则新的Promise需要被拒绝。 - 如果
onFulfilled
或onRejected
不是函数,且promise
的状态已经改变,那么then
方法返回的新Promise的状态应该和原Promise一样(也叫回调穿透
)
// 用于执行回调的辅助函数
function call(fn) {
try {
fn()
} catch (error) {
// onFulfilled或onRejected执行抛出异常需要将新Promise拒绝
reject(error)
}
}
// fulfilled状态直接执行兑现回调
if (this.state === 'fulfilled') {
runTask(() => { // 回调需要异步调用
call(() => {
// 返回了一个值,则进入resolvePromise解析流程
let x = onFulfilled(this.value)
resolvePromise(newPromise, x, resolve, reject)
})
})
return
}
// rejected状态直接执行拒绝回调
if (this.state === 'rejected') {
// 与fulfilled状态处理同理
runTask(() => {
call(() => {
let x = onRejected(this.reason)
resolvePromise(newPromise, x, resolve, reject)
})
})
return
}
// 如果是pending状态,则将回调函数添加到回调队列中
if (this.state === 'pending') {
// 此处存储的回调最终会被resolve或者reject遍历执行,已经是异步执行的了
// 因此此处只要保证把执行异常捕获了就可以
onFulfilled && this.onFulfilledCallbacks.push(() => {
call(() => {
let x = onFulfilled(this.value)
resolvePromise(newPromise, x, resolve, reject)
})
})
onRejected && this.onRejectedCallbacks.push(() => {
call(() => {
let x = onRejected(this.reason)
resolvePromise(newPromise, x, resolve, reject)
})
})
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
以上是then
方法的核心实现,但是上面标准中的提到的回调穿透
并没有实现。回调穿透
指的是当上一个promise
返回的终值或错误在下一个then
回调中没有被处理时,那么下一个then
回调中的onFulfilled
或onRejected
回调会被执行,直到末端。如果未被捕获,则会抛出一个UnhandledPromiseRejectionWarning
警告(此处的警告不是规范的内容,但是常见的宿主环境在实现时都会做此处理)。目前为止,如下代码不能按照规范如期执行:
let p = new Promise(resolve => resolve('success'))
p.then().then(a, b) // 不会接收到'success'
let p2 = new Promise(resolve => reject('fail'))
p.then().catch(err => console.log(err)) // 不会打印err
2
3
4
5
这是因为,第一个then
返回的已经是一个新的Promise
实例,后续的then
和catch
都会被添加到这个实例上,而不是p
上。由于第一个then
没有对p
状态改变后的值进行传递,所以后续的then
和catch
都不会被执行。要解决此问题,只需要在onFulfilled
和onRejected
中不为函数的时候,将其重置为一个仅返回当前终值或者抛出当前异常的函数即可。将以下两处代码做如下修改:
then(onFulfilled, onRejected) {
- onFulfilled = isFunction(onFulfilled) ? onFulfilled : undefined // [!code --]
- onRejected = isFunction(onRejected) ? onRejected : undefined // [!code --]
onFulfilled = isFunction(onFulfilled) ? onFulfilled : value => value // [!code ++]
onRejected = isFunction(onRejected) ? onRejected : reason => { throw reason } // [!code ++]
const newPromise = new Promise((resolve, reject) => {
// ...
onFulfilled && this.onFulfilledCallbacks.push(() => { // [!code --]
this.onFulfilledCallbacks.push(() => { // [!code ++]
...
})
onRejected && this.onRejectedCallbacks.push(() => { // [!code --]
this.onRejectedCallbacks.push(() => { // [!code ++]
//...
})
})
return newPromise
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 5. resolvePromise
resolvePromise
是解析then
方法中返回的值的核心方法,其也是保证Promise
链式调用的关键,也是保证不同实现的thenable
对象之间能够相互操作的关键。接下来,我们就照着Promise/A+
的规范来实现resolvePromise
函数。
我们把一个
promise
记为promise1
,其then
方法返回的新的promise
记为promise2
。
resolvePromise(promise, x, resolve, reject)
:
/**
* @param {Promise} promise promise1.then返回的promise2
* @param {any} x promise1的onFulfilled或onRejected的返回值
* @param {Function} resolve promise2的resolve
* @param {Function} reject promise2的reject
*/
function resolvePromise(promise, x, resolve, reject) {
// 1. 如果promise和x指向同一个对象,以TypeError为据因拒绝执行promise,防止循环引用
if (promise === x) {
return reject(new TypeError('Chaining cycle detected for promise'))
}
let called = false // 处理thenable时,确保resolve和reject只被调用一次
// 2. 如果x是一个promise,则让promise接受x的状态
if (x instanceof Promise) {
// 2.1 如果x处于pending状态,promise必须保持pending状态,直到x被fulfilled或rejected
if (x.state === 'pending') {
x.then(
value => resolvePromise(promise, value, resolve, reject),
reason => reject(reason)
)
} else {
// 2.2 如果x处于fulfilled或rejected状态,则用相同的value执行promise
x.then(resolve, reject)
}
} else if (isFunction(x) || typeof x === 'object' && x !== null) {
// 3. 如果x是一个对象或函数
try {
let then = x.then // 读取其then属性
if (isFunction(then)) { // 3.1 x上具有then方法(thenable对象)
// 3.1.1 将x作为this绑定,执行then方法
then.call(
x,
y => {
if (called) return // resolve只能被执行一次
called = true
resolvePromise(promise, y, resolve, reject)
},
r => {
if (called) return // reject只能被执行一次
called = true
reject(r)
}
)
} else {
// 3.2 x上没有then方法(普通对象),以其值兑现
resolve(x)
}
} catch (e) {
// 检索x.then失败,则需要拒绝promise2
if (called) return
called = true
reject(e)
}
} else {
// 4. 如果x是一个原始值
resolve(x)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
可以看到resolvePromise
的实现还是比较复杂的,要彻底的理解Promise
的运行机制,就必须要理解resolvePromise
的实现,你可以多阅读几次该函数,好好理解和梳理其中的细节。
# 6. runTask辅助函数
Promise/A+
要求resolve
、reject
和then
方法都是异步执行的,所以我们需要将resolve
和reject
方法封装成一个runTask
函数。实现的方式可以使用宏任务,也可以使用微任务:
setTimeout
:宏任务setImmediate
:宏任务process.nextTick
:微任务MutationObserver
:微任务- ...
这里我们使用setTimeout
来实现该异步任务函数
function runTask(task) {
setTimeout(task, 0)
}
2
3
到这里为止,其实Promise/A+
规范的内容就已经全部实现完了,如果用这个实现去跑一下Promise/A+
社区的测试用例,应该就可以全部通过啦。至于Promise
其它的实例方法和静态方法,你可以简单的理解为它们大部分是then
的语法糖或多个Promise
实例的组合方法。下面我们就来实现这些方法。
# 7. catch
与finally
实例方法
(1)catch
实例方法:
catch
方法的实现非常简单,只需要将onRejected
作为then
的第二个参数传入即可。
catch(onRejected) {
return this.then(undefined, onRejected)
}
2
3
(2)finally
方法:
finally
方法是只要Promise
完成(无论成功或失败)都会执行的操作,因此你可能会想到这么实现:
finally(onFinally) {
return this.then(onFinally, onFinally)
}
2
3
但其实finally
的实现是有要求的,它应该满足以下特点:
finally
方法必须返回一个Promise
实例finally
方法无论Promise
的状态如何,都会执行finally
方法返回值会被忽略finally
方法的回调函数不收任何参数finally``Promise
实例的结果会被传递至下一个then
链
如下:
new Promise(resolve => {
resolve(123)
}).then() .finally(res => {
console.log("finally", res)
return 456
}).then(res => {
console.log("then2", res)
})
// 输出
// finally undefined
// then2 123
2
3
4
5
6
7
8
9
10
11
因此,我们可以将onFinally
使用Promise.resolve
包裹后来实现以上特点(你可以先阅读Promise.resolve
的实现再回来看这里)
finally(onFinally) {
return this.then(
value => Promise.resolve(onFinally()).then(() => value),
reason => Promise.resolve(onFinally()).then(() => { throw reason })
)
}
2
3
4
5
6
# 8. 各种静态方法
ECMAScript 6中提供了all
、allSettled
、race
、any
四个组合静态方法以及resolve
、reject
、withResolves
等几个静态方法。
(1)Promise.all
方法
Promise.all()
静态方法接受一个 Promise 可迭代对象作为输入,并返回一个 Promise。当所有输入的 Promise 都被兑现时,返回的 Promise 也将被兑现(即使传入的是一个空的可迭代对象),并返回一个包含所有兑现值的数组。如果输入的任何 Promise 被拒绝,则返回的 Promise 将被拒绝,并带有第一个被拒绝的原因。
实现的基本思路是遍历传入的Promise迭代对象,并记录每个对象的执行结果,当所有Promise都被兑现时,将结果数组作为参数调用resolve
方法。只要有一个Promise被拒绝,则将拒绝原因作为参数调用reject
方法。
static all(promises) {
return new Promise((resolve, reject) => {
const result = [] // 存储结果
let count = 0 // 记录当前兑现的个数
for (let i = 0; i < promises.length; i++) {
const p = promises[i]
p.then(value => {
count++
result[i] = value
if (count === promises.length) { // 已全部兑现
resolve(result)
}
}).catch(reject)
}
})
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(2)Promise.allSettled
方法
Promise.allSettled()
静态方法将一个 Promise 可迭代对象作为输入,并返回一个单独的 Promise。当所有输入的 Promise 都已敲定(无论拒绝还是兑现)时(包括传入空的可迭代对象时),返回的 Promise 将被兑现,并带有描述每个 Promise 结果的对象数组。
实现的基本思路是遍历传入的Promise迭代对象,并记录每个对象的执行结果(每一项为一个包含{status, value | reason}
的对象),当所有Promise都被敲定时,将结果数组作为参数调用resolve
方法。
static allSettled(promises) {
return new Promise((resolve, reject) => {
const results = [] //结果
let count = 0 // 当前敲定个数
for (let i = 0; i < promises.length; i++) {
const p = promises[i]
p.then(value => {
results[i] = { status: 'fulfilled', value } // 记录兑现结果
}).catch(reason => {
results[i] = { status: 'rejected', reason } // 记录拒绝原因
}).finally(() => {
count++
// 已全部敲定
if (count === promises.length) {
resolve(results)
}
})
}
})
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(3)Promise.race
方法
Promise.race()
静态方法接受一个 promise 可迭代对象作为输入,并返回一个 Promise。这个返回的 promise 会随着第一个 promise 的敲定而敲定。
static race(promises) {
return new Promise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
const p = promises[i]
p.then(resolve).catch(reject)
}
})
}
2
3
4
5
6
7
8
(4)Promise.any
方法
Promise.any()
静态方法将一个 Promise 可迭代对象作为输入,并返回一个 Promise。当输入的任何一个 Promise 兑现时,这个返回的 Promise 将会兑现,并返回第一个兑现的值。当所有输入 Promise 都被拒绝(包括传递了空的可迭代对象)时,它会以一个包含拒绝原因数组的 AggregateError 拒绝。
static any(promises) {
return new Promise((resolve, reject) => {
const errors = []
let count = 0
for (let i = 0; i < promises.length; i++) {
const p = promises[i]
p.then(resolve).catch(reason => {
count++
errors[i] = reason
if (count === promises.length) {
reject('none resolved', new AggregateError(errors))
}
})
}
})
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(5)Promise.resolve
方法
static resolve(value) {
// 如果value本身是promise则直接返回
if (value instanceof Promise) return value
// 之所以返回then创建的新promise,是为了让Promise.resolve(thenable)能够正常工作
return new Promise(resolve => resolve(value)).then(value => value)
}
2
3
4
5
6
(6)Promise.reject
方法
static reject(reason) {
return new Promise((resolve, reject) => reject(reason))
}
2
3
(7)Promise.withResolves
方法
Promise.withResolvers()
静态方法返回一个对象,其包含一个新的 Promise 对象和两个函数,用于解决或拒绝它,对应于传入给 Promise() 构造函数执行器的两个参数。
static withResolvers() {
let resolve, reject
const promise = new Promise((_resolve, _reject) => {
resolve = _resolve
reject = _reject
})
return {
promise,
resolve,
reject
}
}
2
3
4
5
6
7
8
9
10
11
12
# 五、测试
Promise
测试通常是借助promises-aplus-tests
(opens new window)这个测试工具。其覆盖了完整的Promise/A+
规范的测试用例,测试通过率达到100%才算完整实现了Promise/A+
规范。
首先安装测试工具:
npm i -g promises-aplus-tests
2
然后是添加一个适配器:测试工具要求实现的Promise库暴漏一个包含以下三个的API:
resolve(value)
(可选):创建一个resolved的Promisereject(reason)
(可选):创建一个rejected的Promisedeferred()
:创建一个延迟对象,并返回一个对象,包含promise
和resolve
、reject
方法
其中resolve(value)
和reject(reason)
已经实现了,如果没有提供,则测试工具会使用deferred
对象自动创建。而deferred
其实就是Promise.withResolvers
方法,也可以使用以下方式再添加:
Promise.deferred = function () { // 延迟对象
let defer = {};
defer.promise = new Promise((resolve, reject) => {
defer.resolve = resolve;
defer.reject = reject;
});
return defer;
}
2
3
4
5
6
7
8
提示
jQuery的异步操作也是借助其Deferred
对象实现的。
把Promise导出
try {
module.exports = Promise
} catch (e) {
}
2
3
4
5
最后执行测试:
promises-aplus-tests promise.js
测试结果是全部通过的,如下:
至此,我们已经把Promise/A+规范以及ECMAScript Promises所有的静态方法都实现了。完整的代码请点击这里查看 (opens new window)