❗实现一个 Promise(2)
除了 then 以外的所有方法
上一篇实现了 Promise 的核心方法 then()
,本篇将在此基础上实现 Promise 的其它 instance method(实例方法)和 static method(静态方法)。
说明:
在基于
then()
实现其它方法时,选择了用then-catch-finally
的形式,主要是为了代码的可读性。理论上,底层库更应该用then()
的双参数形式,毕竟会少返回一两层 promise。这里就不考虑不同的 Promise 实现之间的 interoperability(互操作)了,比如在
resolve()
里直接判断x instanceof MyPromise
,比如在其它方法里直接调用MyPromise.resolve()
。毕竟 Promise 已经成为语言的正式标准也有七八年了,再加上本文的主要目的是想“通过自己实现一个 Promise 来更好地理解其原理”。
1. 实例方法
then()
是 Promise 的 primitive(原始)方法,catch()
和 finally()
都是基于它实现的。
1.1 Promise.prototype.then()
Promise.prototype.then()
MyPromise.prototype.then = function (onFulfilled, onRejected) {
// ...
}
具体的代码实现,详见上一篇:实现一个符合 Promises/A+ 规范的 Promise。
1.2 Promise.prototype.catch()
Promise.prototype.catch()
相当于 then()
的简写,代码如下:
MyPromise.prototype.catch = function (onRejected) {
return this.then(undefined, onRejected)
}
1.3 Promise.prototype.finally()
Promise.prototype.finally()
是在 then()
的基础上实现的,也返回一个 promise(便于链式调用),不过逻辑上有以下不同:
只有一个回调参数,可以理解为形如
then(onFinally, onFinally)
调用时机:无论是 fulfilled 还是 rejected,都会被调用(因此通常用来处理二者的公共逻辑)
在调用链上,如何从前面接值:该回调函数没有参数,即不接收上一个
then
的执行结果在调用链上,如何向后面传值:
如果回调函数的执行正常,则单纯透传上一个
then
的执行结果(包括 fulfilled 和 rejected)如果回调函数的执行被 reject 了,则它会把自身的异常结果传下去,即触发下一个
then
的 reject 逻辑
可以运行以下代码,感受 finally()
的用法:
new Promise((resolve, reject) => {
resolve(1)
// reject(2)
}).finally((value) => {
// 1. 始终输出 undefined,因为它的回调函数没有参数
console.log('finally() value=', value)
// 2. 若是正常结果,则此返回没人理,因为逻辑上是透传上一个的结果
return 3
// 3. 若是有异常了,则会把该异常结果传下去(此时就变成上一个的结果没人理了)
// return Promise.reject('finally reject()')
// throw Error('finally Error()')
}).then((value) => {
console.log('value: ', value)
}, (reason) => {
console.log('reason:', reason)
})
finally()
的代码示意,如下:
MyPromise.prototype.finally = function (cb) {
// 若构造函数未知,则可通过 this.constructor 获取
// 在这个例子里,构造函数就是 MyPromise
// 为了兼容 `cb()` 返回值的各种情况(同步or异步)
// 统一将其处理成 Promise,等其执行完了再返回
return this.then(
(value) => MyPromise.resolve(cb()).then(() => value),
(reason) => MyPromise.resolve(cb()).then(() => { throw reason })
)
}
注意:finally()
的执行顺序取决于它在 then()
链式调用里的顺序,而并不是要“最后”执行。比如:
new Promise((resolve, reject) => {
resolve()
}).finally(() => {
console.log('finally1')
}).then(() => {
console.log('then')
}).finally(() => {
console.log('finally2')
})
// 会输出:
// finally1
// then
// finally2
结合 finally
函数的功能语义,感觉它叫 settled
会更合适。但是考虑到它的使用场景,通常是处理一些不论是 fulfilled 还是 rejected 的通用逻辑,因此大多数情况都是放在链式调用的最后;即便是在中间位置,也是对上一个 then
的 finally 处理。站在这个角度理解,名字叫 finally
也说得通。
2. 静态方法
2.1 Promise.resolve()
Promise.resolve()
要么返回一个新的 promise,要么返回参数本身。
代码示意,如下:
MyPromise.resolve = function (x) {
return (x instanceof MyPromise) ? x : (new MyPromise((resolve) => resolve(x)))
}
通常,当我们不知道某个值是不是 promise 时,就可以用 Promise.resolve(x)
将其显式地转为 promise。
2.2 Promise.reject()
Promise.reject()
返回一个新的 promise 对象,用给定的 reason 来 reject。
MyPromise.reject = function (r) {
return new MyPromise((undefined, reject) => { reject(r) })
}
3. 静态方法之并发
async task concurrency,异步任务并发
JavaScript 本质上是 single-threaded(单线程的),因此在给定的瞬间只有一个 task 被执行,虽然看起来是并发的。JavaScript 中的 parallel execution(并行执行)只能通过 worker threads(工作线程)来实现。
Promise 类提供了 4 种 static method 来处理异步任务并发,包括:
Promise.race()
ES2015,ES6Promise.all()
ES2015,ES6Promise.allSettled()
ES2020Promise.any()
ES2021
它们都接收一个 iterable promises(准确地说是 thenables),然后返回一个新的 promise。其中,iterable 可能包含一个或多个 non-promise value 或已经 settled 的 promise。
3.0 参数说明
(1)参数必须是 iterable
判断一个值是不是 iterable(可迭代的),代码如下:
const isIterable = function (x) {
if (x !== undefined && x !== null && typeof x[Symbol.iterator] === 'function') {
return true
} else {
throw new TypeError(`${typeof x} ${x} is not iterable (cannot read property Symbol(Symbol.iterator))`)
}
}
内置的可迭代对象有 String
, Array
, TypedArray
, Map
, Set
, Segments
以及 arguments
, NodeList
和 function*
, async function*
。
当参数不是 iterable(可迭代的)时,会报错。如下:
rejected: TypeError: undefined is not iterable (cannot read property Symbol(Symbol.iterator))
rejected: TypeError: object null is not iterable (cannot read property Symbol(Symbol.iterator))
rejected: TypeError: boolean true is not iterable (cannot read property Symbol(Symbol.iterator))
rejected: TypeError: boolean false is not iterable (cannot read property Symbol(Symbol.iterator))
rejected: TypeError: number 5 is not iterable (cannot read property Symbol(Symbol.iterator))
rejected: TypeError: object is not iterable (cannot read property Symbol(Symbol.iterator))
(2)当参数是 empty 时
当参数是 empty iterable 时,不同方法的处理结果不同。如下:
race()
必须
永远 pending
all()
必须
fulfilled []
allSettled()
必须
fulfilled []
any()
必须
rejected
AggregateError: All promises were rejected
(3)当包含 non-promise value 时
参数里可能包含 non-promise value,所以统一用 Promise.resolve(p)
处理成 promise。
(4)当包含已经 settled 的 promise 时
对于参数里已经 settled 的 promise,依然按照异步的逻辑执行,即不做特殊处理。
不是 pending 状态的 promise,就称它已经 settled 了,既可以 fulfilled 也可以 rejected。
3.1 Promise.race()
Promise.race()
返回一个新的 promise,用最先 settled 的结果来 settle。
代码示意,如下:
MyPromise.race = function (promises) {
return new MyPromise((resolve, reject) => {
// must be iterable promises
if (isIterable(promises)) {
for (let p of promises) {
MyPromise.resolve(p).then(resolve, reject)
}
}
})
}
3.2 Promise.all()
Promise.all()
返回一个新的 promise,要么是用所有 values 的数组来 fulfill,要么是用最先到的 reason 来 reject。
代码示意,如下:
MyPromise.all = function (promises) {
return new MyPromise((resolve, reject) => {
// must be iterable promises
if (isIterable(promises)) {
const len = promises.length
// empty iterable
if (len === 0) {
resolve([])
}
// iterable
let result = new Array(len)
let count = 0
for (let i in promises) {
MyPromise.resolve(promises[i])
.then((value) => {
count++
result[i] = value
if (count === len) {
resolve(result)
}
}).catch(reject)
}
}
})
}
3.3 Promise.allSettled()
Promise.allSettled()
返回一个新的 promise,用一个 promise state snapshots 数组来 fulfill,且是在所有原始 promises 都 settled 了(可以 fulfilled 也可以 rejected)。
代码示意,如下:
MyPromise.allSettled = function (promises) {
return new MyPromise((resolve, reject) => {
// must be iterable promises
if (isIterable(promises)) {
const len = promises.length
// empty iterable
if (len === 0) {
resolve([])
}
// iterable
let result = new Array(len)
let count = 0
for (let i in promises) {
MyPromise.resolve(promises[i])
.then((value) => {
result[i] = {
status: 'fulfilled',
value
}
})
.catch((reason) => {
result[i] = {
status: 'rejected',
reason
}
})
.finally(() => {
count++
if (count === len) {
resolve(result)
}
})
}
}
})
}
3.4 Promise.any()
Promise.any()
返回一个新的 promise,要么是用最先 fulfilled promise 来 fulfill,要么是用包含所有 reasons 的聚合错误来 reject。
代码示意,如下:
MyPromise.any = function (promises) {
return new MyPromise((resolve, reject) => {
// must be iterable promises
if (isIterable(promises)) {
const len = promises.length
// empty iterable
if (len === 0) {
throw new AggregateError([], 'empty promises')
}
// iterable
let errors = new Array(len)
let errorCount = 0
for (let i in promises) {
MyPromise.resolve(promises[i])
.then((value) => resolve(value))
.catch((reason) => {
errorCount++
errors[i] = reason
if (errorCount === len) {
reject(new AggregateError(errors, 'All promises were rejected'))
}
})
}
}
})
}
Last updated