Promise
Promise 让人又恨又爱的存在,恨是因为面试的时候会围绕它出很多题,又绕又头疼,爱是真香,谁都逃不过真香定律。
1、概念
Promise 是异步编程的一个新的解决方案,阮一峰:ECMAScript 6 入门中给出对 promise 的含义是:所谓 Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。
Promise 是一个构造函数,它有两个特点:
- Promise 有三个状态:pending(进行中)、resolved(已成功)和 rejected(已失败)。并且状态不受外部的影响。
- 状态一旦改变就无法修改。只能有两个过程:一个是从 pending 到 resolved 还有一个就是从 pending 到 rejected,不可能从 resolved 到 rejected,一旦成功就不可能再失败了。
- Promise 一旦创建就会立即执行,不能中途取消。
2、用法
通常我们来说,Promise 主要是解决异步回调地狱。什么是回调地狱?
从网上找了几张图,大家可以感受一下被回调地狱所支配的恐惧:
回调地狱最大的缺点就是代码可读性差,编写费劲。
接下来我们来看一下 Promise 的基本用法:
1 | new Promise((resolve, reject)=>{ |
之前说过,Promise 是一个构造函数,它接收一个函数参数,这个函数中接收两个参数,一个是 resolve 还有一个是 rejected,这两个参数均为函数,并且这两个参数不用自己部署,JS 引擎会自动部署。
resolve 函数的作用是当异步函数成功时,将成功后的值传递出去,同时也是将状态从 pending 变为 resolved,reject 函数的作用是当异步函数失败后,将失败的错误信息传递出去,同时也是将状态从 pending 变为 rejected。
2.1、then()
当 Promise 实例创建成功后,可以执行其原型上的 then 方法,then 方法同样接收两个函数参数,第一个是接收的 resolve 回调的结果,第二个是 reject 回调的结果,第二个参数是非必填的。
1 | new Promise((resolve, reject) => { |
因为 Promise 的对象时立即创建的,所以在 resolve 和 reject 函数之前的操作都会立即执行:
1 | new Promise((resolve, reject) => { |
Promise 执行 then 方法后会返回回来一个新的 Promise 对象,所以可以采用链式调用。
1 | new Promise((resolve) => { |
第一个 then 函数的返回值,可以作为参数传给第二个 then 函数。如果第一个 then 函数返回的依旧是一个 Promise 对象呢?即是一个 Promise 封装的异步操作:
1 | new Promise((resolve) => { |
此时第二个 then 函数传入的参数,即为第一个 then 函数返回的 Promise 对象的 resolved 状态下传递的值。也可以说只有第一个 then 返回的 Promise 执行状态成功时,第二个 then 函数才会执行。
2.2、catch()
除了 then 函数外,在 Promise 原型上还有一个 catch 函数,此函数时当 Promise 内部异步出现错误的时候即为 rejected 状态时,才执行。
1 | new Promise((resolve, reject)=>{ |
当 then 第二个参数和 catch 函数同时存在时,将不会执行 catch 函数:
1 | new Promise((resolve, reject) => { |
那此时的 catch 捕获的是哪个 Promise 的错误呢?捕获的是前一个 Promise 的错误,即 then 函数返回回来的 Promise 错误:
1 | new Promise((resolve, reject) => { |
Promise 的错误有一种类似冒泡机制,当 catch 之前没有没有任何函数截获错误,那终究会被 catch 截获。
1 | new Promise((resolve, reject) => { |
只要 catch 前任何一个 Promise 报错,那终究会被 catch 截获。
通常情况下,不建议使用 then 函数的第二个参数来进行错误的捕获,如上所说的 catch 写法可以捕获前面 then 方法执行中的错误,也更接近同步的写法(try/catch)。因此,建议总是使用 catch()方法,而不使用 then()方法的第二个参数。
then()返回一个新的 Promise 对象,catch()同样返回一个 Promise 对象,同样可以使用链式调用:
1 | new Promise((resolve, reject) => { |
当 catch 捕获完错误后,会接着执行下面的 then 方法,当没有错误抛出时,则会跳过 catch,直接执行后面的 then 方法。但是之后的 Promise 出现错误时,之前的 catch 就捕获不到了。
1 | new Promise((resolve, reject) => { |
catch 后面可以链式调用 then 方法,同样也可以调用 catch 方法,后面的 catch 方法是接收前一个 catch 方法所抛出的错误。
2.3、finally
在 ES8 中新加入了一个方法,即 finally,此方法不同于 then 和 catch,它不跟踪与 Promise 对象状态的改变,即不管 Promise 的最终状态如何,都会执行这个方法,同时 finally 不同于 then 和 catch 地方就是,它不接受任何参数。
1 | new Promise((resolve) => { |
finally 同样返回一个新的 Promise 对象,用法和之前的 then 和 catch 一样,这块就不做过多的讲解了。
3、其他 API
除了上述 Promise 原型上的方法外,Promise 还有很多其他的 API。
3.1、 Promise.all
通过字面意思就能看出来,这个方法是‘全部’意思,由此可见可以接受多个 Promise 对象。
该方法接受具有 Iterator 接口并且每个成员都是 Promise 实例的参数,并返回一个新的 Promise 对象。
1 | let p = Promise.all([ |
并且,只有当接受的参数中所有成员的状态都为 resolved 的时候,p 的状态才会为 resolved,如果有一个成员的状态为 rejected,那 p 的状态就为 rejected。
当所有成员的状态均为 resolved 的时候,会将每个成员 resolved 状态下的值拼成数组传递给 p 的回调函数。
1 | let p = Promise.all([ |
当有一个成员的状态为 rejected 的时候,则会将第一个 rejected 状态的值返给 p 的 catch 方法。
1 | let p = Promise.all([ |
如果有一个成员为 rejected 状态,并且自身调用了 catch 方法,那将不会走 p 对象的 catch 方法,这一点要注意。
3.2、 Promise.race
race 翻译成中文是竞赛的意思,他表示多个 Promise 对象,哪个成员率先改变状态,那 Promise.race 返回的 Promise 对象的状态变为什么,并将值转递给 p 的回调函数,它和 Promise.all 接收的参数一样。
1 | let p = Promise.race([ |
3.3、Promise.allSettled
该方法是 ES2020 新加入的,和 all 一样,返回一个新的 Promise 对象,接收一组 Promise 对象,但是和 all 区别的是,当不管每个成员的 Promise 是什么状态,只要执行结束,则返回的 Promise 对象就会执行结束。
1 | let p = Promise.allSettled([ |
有时候异步请求并不在意是否能够成功,这个时候这个方法就很符合场景了,并且返回一个数组,数组中每个对象有两个状态,一个是 fulfilled,另一个是 rejected,返回之后可以进行筛选,查看错误信息。
3.4、Promise.resolve
将一个对象转化为一个 Promise 对象。
1 | Promise.resolve("foo"); |
当 Promise.resolve 的参数是一个 Promise 实例时,原封不动的返回这个实例:
1 | let p = new Promsie((resolve) => resolve(1)); |
当参数是一个 thenable 对象时,即含有 then 方法的对象时,会返回一个 Promise 对象,并立即执行 then 方法。
1 | let thenable = { |
当参数是不是一个 thenable 对象时,由于参数不是一个异步的,所以当 Promise.resolve 后,直接的状态就是 resolved 的状态,所以 then 后就会输出原值。
1 | 1. 参数是个普通对象 |
当不传参数的时候,返回的就是一个带有 resolved 状态的 Promise 对象。
3.5、Promise.reject
返回一个状态为 rejected 的 Promise 对象,传入的参数作为错误信息作为后续方法传递的参数。
1 | let num = "1"; |
当参数是 thenable 对象时,返回的不是 error 信息而是 thenable 对象。
1 | let thenable = { |