基本概念
Promise
是异步编程的一种解决方案,被用于表示一个异步操作的最终完成 (成功或失败),及其结果值。ES6将其写进了语言标准,统一了用法,原生提供了Promise
对象,得回调变得灵活易懂,也解决了异步回调地狱的问题。
说人话,就是 Promise 一般用来包裹异步任务,等待任务完成的结果,并返回其结果的成功和失败两种情况的回调,可以在相应结果的回调中作出对应的处理。
注意:考虑到篇幅和阅读体验,全文分上下两篇:使用篇、原理篇。本文只涉及到使用部分,下篇会详解原理,几个关键问题,并尝试手撸一份 MyPromise
。
状态
Promise
有三种状态:
- pending:初始化的状态
- fulfilled:成功的状态
- rejected:失败的状态
状态只能由 pending -> fulfilled
或者是 pending -> rejected
两种情况,且状态变化只可能发生一次。
初体验
创建一个Promise
对象(起始默认为pending
状态):
const p = new Promise(executor)
如:
其中:
executor
: 执行函数(resolve, reject) => {}
resolve
: 定义成功时我们调用的函数value => {}
reject
: 定义失败时我们调用的函数reason => {}
注意:executor 会在 Promise 内部立即同步调用,异步操作在执行函数中执行。
场景:AJAX请求
发起一个常见的一个ajax请求:
控制台输出:
css
{
message: "https://images.dog.ceo/breeds/dhole/n02115913_4117.jpg"
status: "success"
}
then()
then
方法上文也使用到了,有两个可选参数,分别是成功和失败的回调函数:
resolved
:成功状态,执行第一个回调函数rejected
:失败状态,执行第二个回调函数
结果 | 代码 | 输出 |
---|---|---|
成功 | success:ok |
|
失败 | failure:error |
并且默认返回一个新的Promise
对象,新Promise
的结果状态由then()指定的回调函数执行的结果决定的。
情况1:正常情况
如上调用resolve()
和 reject()
两种情况,then()
中的成功和失败的回调函数中正常处理逻辑,并没有显示的 return
或 throw
抛出错误。
此时的返回新的Promise
对象的状态就是 fulfilled
(成功):
情况2:返回失败
如上,只要在then()
的任意一个回调函数中抛出一个错误。
此时的返回新的Promise
对象的状态就是 rejected
(失败):
情况3:返回非 Promise 的任意对象
如上,在then()
的任意一个回调函数中显示得 return
一个任意值。
此时的返回新的Promise
对象的状态就是 fulfilled
(成功):
情况4:返回 Promise 对象
返回不同状态的Promise 对象 | 代码 | 输出 |
---|---|---|
成功的 Promise 对象 | ||
失败的 Promise 对象 |
如上,在then()
的任意一个回调函数中显示得 return
一个Promise 对象。这个Promise 对象的结果就会成为默认返回的新Promise
的结果。
catch()
用来指定失败的回调函数。上述说过then()
中的两个回调函数都是可选的,可以使用 catch()
来只处理失败的回调。
当存在链式调用多个执行时,不需要每一次都写失败回调,可以统一到最后使用 catch()
。若有一个Promise
的状态为reject
的话,则会将异常一直向下穿透直到catch()
回调。
如下:
输出: error
链式调用
上面说到的then()
会默认返回一个新的Promise
对象,因此就可以方便地进行链式调用。
如上,调用了两次then()
,按照上述的情况,在then()
中处理不同的回调函数,前一个回调函数中的返回值作为后一个回调函数的参数。
finally()
finally
是在ES9(ES2018)中新增的一个特性:表示无论Promise
对象变成 fufilled
还是rejected
状态,最终都会被执行。除非是开发者显式终止了链式调用,这个下文会讲到。
finally
方法中的回调函数
是不接受参数的,因为无论前面是 fulfilled
状态还是 rejected
状态,它都是执行。
如下:
输出:
静态方法
提供了以下的常用静态方法
Promise.resolve()
将一个普通值转为Promise
类型的数据。
参数 | 代码 | 输出 |
---|---|---|
非promise对象 | ||
Promise对象 |
结论:
- 若参数为非
Promise
对象,则返回的结果为成功状态的Promise
对象 - 若参数为
Promise
对象,参数的状态决定返回结果的状态
Promise.reject()
用法同上,将一个普通值转为Promise
类型的数据。返回的结果始终为失败的回调函数。
输出:
Promise.all()
- 针对于多个
Promise
的异步任务进行处理 - 参数:
Promise
数组 - 返回值:
Promise
对象,状态由数组中的对象状态
决定- 若每个对象状态都成功,则返回的
Promise
对象的状态为成功,且成功的结果值为每个Promise
对象成功结构值组成的数组。 - 若其中一个对象状态为失败,则返回的
Promise
对象的状态为失败,且失败的结果值为失败的Promise
对象的结果值。
- 若每个对象状态都成功,则返回的
简单来说就是有两种情况,类似逻辑中 &
的关系,必须全部成功才返回成功,只要一个失败则返回失败,
情况 | 代码 | 输出 |
---|---|---|
全部成功 | ||
存在失败 |
Promise.allSettled()
用来确定要一组异步操作是否都结束了(不管成功或失败)。
- 参数:
Promise
数组 - 返回值:成功的
Promise
对象,结果值是由每个 Promise 对象的状态和结果值组成对象的数组
用法跟 Promise.all()
相似,如下:
输出:
Promise.race()
race
有比赛、赛跑的意思,即接受一个Promise
对象的数组,比赛谁的状态最新改变。
- 参数:
Promise
数组 - 返回值:
Promise
对象,其对象和结果值均由最先改变状态的 Promise 对象决定
如下:
输出:
使用了 setTimeout
(是宏任务,不严谨) 模拟了异步任务,看结果果然 为爱冲锋是最快的啊! 。与 Promise.all
相似的是,Promise.race
都是以一个 Promise 对象组成的数组作为参数。不同的是,只要当数组中的其中一个 Promsie 状态变成 resolved 或者 rejected 时,就可以调用then()
。
Promise.any()
- 参数:
Promise
数组 - 返回值:
Promise
对象,状态存在两种情况- 数组中只要有一个
Promise
对象为成功状态,则返回的Promise
对象的状态为成功,结果值为最先状态变为成功的对象的结果值。 - 若数组中所有对象都是失败状态,则返回的
Promise
对象的状态为失败。
- 数组中只要有一个
简单来说,就是类似逻辑运算中 |
的关系,只要有一个成功则成功;必须全部失败才失败,且不会因为某个对象变成 rejected
状态而结束,必须等到所有的对象状态均变为失败。
成功的情况:
输出:
失败的情况:
输出:
终止 Promise 链条
在链式调用的时候,如果想要在某一个链条终止整个调用,有且只有一种方式:返回一个 pending
状态的 Promise 对象。
如下:
输出:
很好理解,返回了一个pending
状态的 Promise 对象,状态没有改变就不会触发后续的回调,且 finally()
方法也不会执行。
async / await
async / await 是ES7提出的基于Promise的解决异步的最终方案。
async
async
是一个加在函数前的修饰符,被 async
定义的函数会默认返回一个 Promise
对象 resolve
的值,即始终是成功状态 (特例就是当返回一个 pending 状态的 Promise 对象时) 。所以对 async
函数可以直接使用 then()
,返回值就是then()
传入的函数。
基础使用如下:
输出:
上文函数中返回有3种情况,非Promise对象、成功的Promise 、失败的reject。调用后默认返回均是成功状态的 Promise。
特例:若返回了一个pending
状态的 Promise 对象,则调用后默认返回就是pending
状态的 Promise 对象,且后续的链式回调不再执行。
await
await
也是一个修饰符,只能放在 async
定义的函数内。
- 修饰 Promise 对象,可以获取 Promise 中返回的内容,即
resolve()
或reject()
的参数,且取到值后语句才会往下执行,即同步执行。 - 修饰非 Promise 对象:把这个非 Promise 的东西当做 await 表达式的结果。
- await 必须写在 async 函数中,但是 async 函数中可以没有 await 。
- 如果 await 的 Promise 失败了,就会抛出异常,需要通过
try...catch
捕获处理。
输出:
个人的学习总结,有误的地方欢迎指正!