Promise上的一些工具函数的实现
前言
之前写过手写 Promise 的实现,这次来写写它的一些工具函数和对象函数的实现
本文基于之前Promise
以及相关then
方法。
之前自定义的Promise
函数名字为MyPromise
,下文都以MyPromise
来表示
正文
Promise.resolve
根据文档上的解析
Promise.resolve(value)
方法返回一个以给定值解析后的Promise
对象。如果这个值是一个Promise
,那么将返回这个Promise
;如果这个值是thenable
(即带有"then"
方法),返回的Promise
会“跟随”这个thenable
的对象,采用它的最终状态;否则返回的Promise
将以此值完成。此函数将类Promise
对象的多层嵌套展平。
可以看到Promise.resolve
是以参数value
的类型来决定如何处理返回的Promise
,而不是一味的resolve
掉这个Promise
看到网上有如下的实现
1 | MyPromise.resolve = function (val) { |
这样实现是百分百不对的,可以测试下面的代码
1 | Promise.resolve(Promise.reject(1)).then( |
输出是执行第二个参数的回调,也就是输出err: 1
,如下
所以Promise.resolve
只是通过传入参数value
来决定,所以实现如下
1 | MyPromise.resolve = function (val) { |
Promise.reject
Promise.reject
就没有Promise.resolve
复杂,它的逻辑就非常单纯,就是以传入的值拒绝返回的Promise
对象即可,比如
1 | Promise.reject(Promise.resolve(1)).then( |
上面这个Promise
会以Promise.resolve(1)
作为失败原因被拒绝
那么实现如下
1 | MyPromise.reject = function (val) { |
Promise.prototype.catch
catch
方法用于注册一个Promise
被reject
时的回调,所以可以使用then
方法来实现catch
(可以说catch
是一个特殊的then
)
1 | MyPromise.prototype.catch = function (onRejected) { |
由于then
会返回新的Promise
,所以无需我们去创建一个新的Promise
返回
Promise.prototype.finally
finally
方法注册的回调不管在resolved
状态或者rejected
状态都会进行执行,所以根据catch
很容易写出下面的实现代码
1 | MyPromise.prototype.finally = function (onFinally) { |
但这么写其实是有问题的,在提案上(现在已经为ecma
的标准了)也有解释为啥不适用then(f, f)
这种方式
finally
方法的提案地址:tc39 / proposal-Promise-finally
在README.md
中有一段
Why not
.then(f, f)
?
为什么不使用.then(f, f)
Promise.finally(func)
is similar toPromise.then(func, func)
, but is different in a few critical ways:
Promise.finally(func)
和Promise.then(func, func)
有点类似,但是在一些比较关键的地方有所不同。
- When creating a function inline, you can pass it once, instead of being forced to either declare it twice, or create a variable for it
- A
finally
callback will not receive any argument, since there’s no reliable means of determining if the Promise was fulfilled or rejected. This use case is for precisely when you do not care about the rejection reason, or the fulfillment value, and so there’s no need to provide it.- Unlike
Promise.resolve(2).then(() => {}, () => {})
(which will be resolved with undefined),Promise.resolve(2).finally(() => {})
will be resolved with 2.- Similarly, unlike
Promise.reject(3).then(() => {}, () => {})
(which will be resolved with undefined),Promise.reject(3).finally(() => {})
will be rejected with 3.
- 当创建一个内联的函数,你只需要传递一次,而不需要强制传递两次声明的函数,或者不需要为这个函数创建一个变量来保存
- 一个
finally
回调不会接收任何参数,因为没有可靠的方法来判断Promise
是否已经被解决或者被拒绝。当你不关心Promise
被拒绝的原因或者被解决的值时使用finally
会更加精确。所以不需要提供参数。 - 不像
Promise.resolve(2).then(() => {}, () => {})
(这个Promise
将会以undefined
为值被解决),Promise.resolve(2).finally(() => {})
将会以2
为值被解决 - 类似地,不像
Promise.reject(3).then(() => {}, () => {})
(这个Promise
将会以undefined
为值被解决),Promise.reject(3).finally(() => {})
将会以3
为值被拒绝
However, please note: a
throw
(or returning a rejected Promise) in thefinally
callback will reject the new Promise with that rejection reason.
然而,请注意:在finally
中抛出(或者返回一个被拒绝的Promise
对象)会使得新的Promise
对象以这个原因被拒绝。
我们可以测试下原生的 Promise 的情况
情况 1
1 | const Promise = Promise.resolve("Promise.resolve"); |
这时根据规范,最后一个then
的onResolved
中的res
应该是"Promise.resolve"
测试结果符合规范
情况 2
1 | const Promise = Promise.resolve("Promise.resolve"); |
这时根据规范,最后一个then
的onResolved
中的res
应该依然是"Promise.resolve"
,而不是"finally Promise.resolve"
测试结果符合规范
情况 3
1 | const Promise = Promise.resolve("Promise.resolve"); |
这时根据规范,finally
中返回了一个reject
的Promise
,那么要以这个Promise
的拒绝原因来拒绝新的Promise
对象。
那么最后一个then
应该走catch
的回调,然后对应参数err
为"finally Promise.reject"
。
综上,我们讨论了Promise.resolve
情况下finally
的情况,还有Promise.reject
情况下finally
的情况。
但这两种情况对于finally
的情况是一样的,所以这里不做测试,大家可以自行测试下。
所以,实现上必须和上面的行为保持一致
1 | MyPromise.prototype.finally = function (onFinally) { |
可以看到实现都是通过MyPromise.resolve
来包装onFinally
执行后的返回值,对于resolved
的情况就返回原来Promise
的value
或者err
,如果rejected
那么抛出对应原因,也就符合了规范
可以发现,第二个参数的回调都是直接返回传入的值,所以可以省略
1 | MyPromise.prototype.finally = function (onFinally) { |
Promise.all
MDN 上对Promise.all
的解释如下:
Promise.all(iterable)
方法返回一个Promise
实例,此实例在iterable
参数内所有的Promise
都“完成(resolved)”或参数中不包含Promise
时回调完成(resolve);如果参数中Promise
有一个失败(rejected),此实例回调失败(reject),失败的原因是第一个失败Promise
的结果。
简单点讲,就是全部成功,返回的Promise
才成功,只要一个失败,返回的Promise
就失败。
那么对于全部这个状态,就需要有一个变量来计算当前是否已经全部完成。
以及对于数组中的非Promise
对象,要包装成Promise
。
实现如下
1 | MyPromise.prototype.all = function PromiseAll(promiseArr) { |
Promise.allSettled
MDN 上解释如下
该
Promise.allSettled()
方法返回一个在所有给定的Promise
都已经fulfilled
或rejected
后的Promise
,并带有一个对象数组,每个对象表示对应的Promise
结果。
当您有多个彼此不依赖的异步任务成功完成时,或者您总是想知道每个
Promise
的结果时,通常使用它。
简单点讲就是每个回调不管成功失败都放进结果,然后全部处理之后在解决返回的Promise
。
1 | MyPromise.allSettled = function (promiseArr) { |
上面的实现中,每个对象为{ status: "Promise的状态", value: "对应的值"}
这是规范中所定义的,目前Promise.allSettled
已经在 es2020 中添加了
规范地址:proposal-Promise-allSettled
Promise.any
MDN 上的解释如下
接收一个
Promise
可迭代对象,只要其中的一个Promise
成功,就返回那个已经成功的Promise
。如果可迭代对象中没有一个Promise
成功(即所有的Promises
都失败/拒绝),就返回一个失败的Promise
和AggregateError
类型的实例,它是Error
的一个子类,用于把单一的错误集合在一起。本质上,这个方法和Promise.all()
是相反的。
解释上也很明显指出了,“和Promise.all
相反”,也就是所有都失败才返回失败,一个成功就返回成功
1 | MyPromise.any = function (promiseArr) { |
对于拒绝对象{ status: "AggregateError: No Promise in Promise.any was resolved", errArray}
为提案中所定义的,所以这里我们直接构造即可,该 API 会在 es2021 中被加入
Promise.race
MDN 上解释如下
Promise.race(iterable)
方法返回一个Promise
,一旦迭代器中的某个Promise
解决或拒绝,返回的Promise
就会解决或拒绝。
简单点讲就是竞速,谁快谁决定,这个是比较好实现的
1 | MyPromise.race = function PromiseRace(promiseArr) { |