zl程序教程

您现在的位置是:首页 >  后端

当前栏目

细读 ES6 | Promise 下篇

ES6 Promise 下篇
2023-09-27 14:25:57 时间
前言

继续介绍 Promise 相关 API。


正文一、Promise.resolve()


Promise.resolve() 方法的作用就是将某个值 非 Promise 对象实例 转换为 Promise 对象实例。

const promise Promise.resolve( foo )

// 相当于

const promise new Promise(resolve resolve( foo ))


需要注意的是 它仍然会遵循 Event Loop 机制 包括后面介绍的其他 API。具体执行顺序本文不展开讨论。


Promise.resolve() 方法的参数分为四种情况


1. 不带任何参数


它返回一个状态为 fulfilled 值为 undefined 的 Promise 实例对象。

const promise Promise.resolve()

// promise 结果 

// [[Prototype]]: Promise,

// [[PromiseState]]: fulfilled ,

// [[PromiseResult]]: undefined

// }


2. 参数是一个 Promise 实例对象


这时 Promise.resolve() 将会不做任何修改、原封不动地返回该实例。

请注意 即使参数是一个 rejected 状态的 Promise 实例 返回的实例也不会变成 fulfilled 状态 不要被这个 resolve 字面意思误解了。

const p1 new Promise(resolve resolve({ name: Frankie })) // fulfilled 

const p2 new Promise((resolve, reject) reject({ name: Frankie })) // rejected 

const p3 Promise.resolve(p1)

const p4 Promise.resolve(p2)

console.log(p1 p3) // true

console.log(p2 p4) // true

// p3 结果 

// [[Prototype]]: Promise,

// [[PromiseState]]: fulfilled ,

// [[PromiseResult]]: { name: Frankie }

// p4 结果 

// [[Prototype]]: Promise,

// [[PromiseState]]: rejected ,

// [[PromiseResult]]: { name: Frankie }

// }

const p5 new Promise(resolve resovle(1))

const p6 new Promise(resolve {

 reslove(p5)

 // 注意 不要尝试在此处调用 Promise.resolve() 会导致无限递归。

})


上面示例中 p6 的状态取决于 p5 的状态。


3. 参数是一个 thenable 对象


thenable 对象 是指具有 then 方法的对象。例如

const obj {

 then: function(resolve, reject) {

 resolve( foo )

}


上面示例中 obj 对象就是一个 thenable 对象。Promise.resolve() 方法会将这个 thenable 对象转为 Promise 对象 然后就立即执行 thenable 对象的 then() 方法。

const obj {

 then: function (resolve, reject) {

 console.log(2)

 resolve( foo )

 // reject( foo ) // 如果是这样 最终 promise 对象将会变成了 rejected 状态。

const promise Promise.resolve(obj)

promise.then(res {

 console.log(3)

 console.log(res) // foo 

console.log(1)

// 打印结果分别是 1、2、3、 foo 

// promise 结果 

// [[Prototype]]: Promise,

// [[PromiseState]]: fulfilled ,

// [[PromiseResult]]: foo 

// }


上述示例中 obj 对象的 then() 方法执行后 对象 promise 的状态变成了 fulfilled 接着执行最后的那个 promise.then() 方法 打印出 foo 。


4. 参数是一个不具有 then() 方法的对象 或者压根不是一个对象 而是原始值。


如果是这种情况 Promise.resolve() 方法返回一个新的 Promise 对象 状态为 fulfilled 且该实例对象的值 就是该参数值。

const p1 Promise.resolve( foo )

const p2 Promise.resolve({})

// p1 结果 

// [[Prototype]]: Promise,

// [[PromiseState]]: fulfilled ,

// [[PromiseResult]]: foo 

// p2 结果 

// [[Prototype]]: Promise,

// [[PromiseState]]: fulfilled ,

// [[PromiseResult]]: {}

// }


在实际项目中 一般是第 4 种情况居多 我似乎真的没见过前三种情况的。


二、Promise.reject()


Promise.reject() 方法会返回一个新的 Promise 实例对象 该实例的状态总是为 rejected。

const promise Promise.reject( foo )

// 相当于

const promise new Promise((resolve, reject) reject( foo ))

跟 Promise.resolve() 不同的是 Promise.reject() 方法的参数 无论是原始值、普通对象、还是 Promise 实例对象 将会原封不动地作为返回实例对象的值。

Promise.reject( Oops ).catch(err {

 console.log(err Oops ) // true

 // do something...

})


三、Promise.all()


Promise.all() 方法用于将多个 Promise 实例 包装成一个新的 Promise 实例。

const promise Promise.all([p1, p2, p3])


上面代码中 Promise.all() 方法接受一个数组作为参数 其中 p1、p2、p3 都是 Promise 实例。如果数组中包含非 Promise 实例 它们会使用 Promise.resolve() 的方法 将参数转为 Promise 实例 再进一步处理。另外 Promise.all() 方法的参数可以不是数组 但必须具有 Iterator 接口 且返回的每个成员都是 Promise 实例。

其中 promise 的状态由 p1、p2、p3 决定 分为两种情况。

只有当 p1、p2、p3 的状态都变成 fulfilled promise 的状态才会变成 fulfilled 此时 p1、p2、p3 实例的值 会组成一个数组 并传递给 promise。
只要 p1、p2、p3 之中有一个被 rejected promise 的状态就会变成 rejected。此时第一个 rejected 实例的值 注意 不会像上面一样组成数组哦 会传递给 promise。


看个例子

const userIds [1, 3, 5]

const promiseArr userIds.map(id {

 return window.fetch( /config/user/${id} ) // 假设是请求用户配置

Promise

 .all(promiseArr)

 .then(res {

 // res 是一个数组 每一项对应每个实例的值 即 [[PromiseResult]]

 // 常见做法是将 res 进行解构 即 Promise.all(promiseArr).then(([a, b, c]) { /* do something... */ })

 // 假设 promiseArr 是一个空的可迭代对象 例如空数组 Promise.all([]) 实例状态为 fulfilled 值为 []。

 // do something...

 .catch(err {

 // err 为 Promise.all() 被 rejected 的原因 reason 

 })


上面的示例中 promiseArr 是包含 3 个 Promise 实例的数组 只有这 3 个实例的状态都变成 fulfilled 或其中一个变为 rejected 才会调用 Promise.all() 方法的回调函数。


四、Promise.race()


Promise.race() 方法同样是将多个 Promise 实例 包装成一个新的 Promise 实例。

const promise Promise.race([p1, p2, p3])


Promise.race() 方法同样接受一个可迭代对象 只要 p1、p2、p3 中有一个实例率先改变状态 fulfiled 或 rejected promise 的状态就会跟着改变 而且 promise 实例的值就是率先改变的实例的返回值。若可迭代对象中的某一项不是 Promise 实例 仍会使用 Promise.resolve() 进行转换。

当传递一个空的可迭代对象 那么 Promise.race() 实例的状态将会一直停留在 pending。这点跟 Promise.all() 是不同的。

const p1 Promise.all([])

const p2 Promise.race([])

setTimeout(() {

 console.log(p1) // Promise { fulfilled : Array(0)}

 console.log(p2) // Promise { pending }

})


五、Promise.allSettled()


Promise.allSettled() 是 ES11 标准引入的一个方法 同样还是将多个 Promise 实例包装成一个新的 Promise 实例。只有等所有实例都返回结果 无论是 fulfilled 还 rejected 包装实例的状态才会发生变化。


我认为 这算是对 Promise.all() 存在 rejected 实例情况的一种补全吧。


注意 Promise.allSettled() 的状态 只可能是 pending 或 fulfilled 状态 不可能存在 rejected 状态。即只会从 pending - fulfilled 的变化。


我们来看看以下示例 各种情况的结果吧

const p1 Promise.reject(1)

const p2 Promise.resolve(2)

const p3 new Promise((resolve, reject) {

 setTimeout(() { reject(3) }, 1000)

const p4 new Promise(() { }) // p4 状态将会一直停留在 pending

const p5 Promise.allSettled([]) // 参数为空迭代对象

const p6 Promise.allSettled([p4])

const p7 Promise.allSettled([p1, p2, p3])


}, 2000)


2.webp.jpg


列举以上示例 是为了得出以下结论


Promise.allSettled() 一定要等到参数中每一个 Promise 状态定型后 它返回的实例对象才会定型为 fulfilled 状态。否则只会是 pending 状态。
类似 Promise.allSettled([]) 把一个空数组 空的迭代对象 作为参数 最后实例的状态为 fulfilled 且实例的值为空数组 []。
注意 Promise.allSettled() 返回的实例的值 首先它是一个数组 而数组每项都是一个对象 该对象的属性取决于对应参数 Promise 实例的状态。
例如 p1 的状态为 rejected p2 的状态为 fulfilled。因此包装实例的前两项的对象分别为 { status: rejected , reason: 1 }、{ status: fulfilled , value: 2 } 其他项同理。其中 status 属性只会是 fulfilled 或 rejected 两个字符串值。主要区别在于 value 属性和 reason 属性 即 fulfilled 状态对应 value 属性 而 rejected 状态对应 reason 属性。


有时候 我们不关心异步操作的结果 只关心这些操作有没有结束。这时 使用 Promise.allSettled() 方法就很有用了。而 Promise.all() 是没办法确保这一点的。


六、Promise.any()


在 ES12 标准中 引入了 Promise.any() 方法 它用于将多个 Promise 实例 包装成一个新的 Promise 实例。


Promise.any() 接受一个 Promise 可迭代对象 只要参数实例中有一个变成 fulfilled 状态 包装实例就会变成 fulfilled 状态 其值就是参数实例的值。

Promise.any() 与 Promise.race() 很像 只有一个不同点 就是 Promise.any() 不会因为某个参数 Promise 实例变成 rejected 状态而接受 必须要等到所有参数实例的状态都变为 rejected 包装实例的状态才会是 rejected。

const p1 Promise.reject(1)

const p2 Promise.resolve(2)

const p3 new Promise((resolve, reject) {

 setTimeout(() { reject(3) }, 1000)

const p4 new Promise(() { }) // p4 状态将会一直停留在 pending

const p5 Promise.any([]) // p5 会变成 rejected 状态

const p6 Promise.any([p4])

const p7 Promise.any([p1, p2, p3])


}, 2000)


0.webp.jpg


当 Promise.any() 返回的实例变成 rejected 时 其实例的值是 AggregateError 实例。但传递一个空的迭代对象 Promise.any() 包装实例也会变成 rejected 状态 如 p5。


七、总结


关于 Promise.all()、Promise.race()、Promise.allSettled()、Promise.any() 方法 总结以下特点。

它们的用处都是将多个 Promise 实例 包装成一个新的 Promise 实例。
它们都接受一个具有 Iterator 接口的可迭代对象 通常为数组。且会返回一个新的 Promise 实例对象。
它们处理参数为空的可迭代对象的方式不一样 本来就是要处理多个 Promise 对象 才会用到它们 所以这种情况无需理会。真遇到再回来翻阅文档即可 现在我写到这里都记不太清楚其中的区别了 但问题不大。
Promise.all() 当所有实例均为 fulfilled 状态 最终的包装实例才会是 fulfilled 其值是一个数组。否则将会是 rejected 状态
Promise.race() 则是某个实例的状态发生变化 最终包装实例将对应率先变化实例所对应的值和状态。“发生变化”是指 pending - fulfilled 或 pending - rejected。
Promise.allSettled() 单从命名上来猜测 就知道它需要等所有参数实例确定状态后 包装实例的状态才会变成 fulfilled 状态 注意它不存在 rejected 状态的情况。包装实例的返回值是一个数组 数组每项可能是 { status: fulfilled , value: /* 对应 fulfilled 的值 */ } 或 { status: rejected , reason: /* 对应 rejected 的原因 */ } 取决于每个参数实例的状态。
Promise.any() 当某个参数实例的状态变为 fulfilled 那么包装实例就定型了 对应该参数实例的状态和值。否则它必须等到所有参数实例变为 rejected 状态 包装实例的状态才会发生改变 变为 rejected 其值是一个 AggregateError 实例。


The end.



【温故知新】5 个 Promise 要避免的常见用法~ 本瓜一直觉得 Promise 就是咱 JS 人的浪漫,没错,Promise 天天见,但或许越熟悉越陌生,我们在一直用的过程中会形成一些定式,这导致难免会漏掉一些定式以外的要点;
十五、这一次,彻底弄懂Promise的使用 Promise的重要性我认为没有必要多讲,概括起来说就是四个字:必!须!得!掌!握!。 而且还要掌握透彻。这篇文章的开头,主要分析一下,为什么会有Promise出现。 在实际的使用中,有非常多的应用场景我们不能立即知道应该如何继续往下执行。最常见的一个场景就是ajax请求。通俗来说,由于网速的不同,可能你得到返回值的时间也是不同的,这个时候我们就需要等待,结果出来了之后才知道怎么样继续下去。