JavaScript在ES6版本中新增了Promise
对象。
概述
Promise
对象是一个代理对象(代理一个值),被代理的值在Promise对象创建时可能是未知的。它允许你为异步操作的成功和失败分别绑定相应的处理方法(handlers)。
以上引用自MDN中对Promise的介绍。
Promise
其实就是一个对异步操作结果的封装。一个Promise
有三种状态:
- Pending: 初始状态,既不是成功,也不是失败状态。
- Fulfilled: 表示操作成功完成。
- rejected: 表示操作失败。
Promise
翻译为保证,承诺的意思。这意味着一个Promise
一旦有了结果,就不会再发生改变。Pending是初始状态,此时表明还没有获取到结果。一旦状态从Pending
改变为Fulfilled
或rejected
后,状态便不会再发生改变。
在之前的ES5时代,我们通常会用回调函数来处理异步操作的结果。
1 | function successCallback() { |
上面的代码先是封装了两个回调函数,分别是成功和失败。然后在test函数内执行异步请求(忽略),根据异步请求的结果来执行对应的回调函数。如果在只有一层异步请求的时候,代码结构还算清晰。但实际业务中,往往会嵌套多层异步请求,层数一多,还用这种写法层层嵌套的话,代码会变得难以阅读和维护,也就是经典的回调地狱模式。所以这种情况就可以使用ES6新增的Promise
方法。
Promise
对象使用new操作符创建,接收一个函数作为参数,该函数又接收resolve和reject两个函数,这两个函数是ES6帮我们封装好的,我们直接调用就可以了。resolve
表示成功时执行的函数,而reject
表示失败时执行的函数。两个函数都可以传参,后期在.then里接收。
Promise.then()
.then()
方法用于Promise
实例的状态改变时执行的回调函数。它接收两个参数,分别是状态成功时和状态失败时执行的回调函数,同时两个函数内可以传递参数供回调函数使用。
下面我们来创建一个Promise
实例并用.then()方法执行相应的回调函数。
1 | const a = 3 |
上面的代码我们首先声明了一个属性a,紧接着在p函数里封装了一个Promise
对象并返回。里面假设进行了一个异步请求。如果异步请求执行成功,执行resolve
函数,并把a当作参数传递进去。如果异步请求执行失败,执行reject
函数,同时也把参数a传递进去。之后我们执行了p函数,将参数a传递进去,并在.then()内写了两个箭头函数用于执行成功和失败的回调。这时运行代码控制台会输出:
1 | success! |
运行到这里说明走了resolve函数,并且获取到了传进来的参数a。如果此时将p函数内的异步操作结果修改为false
,就会执行reject
函数内的代码了。这里需要注意的是,success虽然写在resolve
的后面,但是先于resolve
执行的。因为在Promise
对象里,同步代码总是先于异步代码执行。
这就是Promise
的用法。使用Promise
来封装异步请求,结构清晰,写法简明,即使有多层嵌套也不会出现回调地狱的情况。.then()方法有一个链式调用的特点。一个Promise
对象会返回一个新的Promise
对象,所以如果都是Promise
对象的话,可以这样用(前提是都用Promise
包装起来)。
1 | // 以下Promise对象省略了reject函数 |
上面的代码首先模拟了一个后台请求到的数据,一个json对象,里面包含了data对象,data对象下是一个数组。接下来分别封装了p1, p2, p3三个Promise对象函数。每一层内用setTimeout定时器模拟了执行异步请求,间隔1秒。在定时器里把当前接收到的数据的下一层打印出来,同时以resolve
函数参数的形式传递出去。最后执行.then()方法的时候,我们可以看到,每一层函数都打印出了上一层传递进来的数据。也就是说,上层Promise
对象传递出来的属性,.then()方法可以接收到。
Promise.catch()
.catch()方法可以捕获到Promise
对象内的错误。
1 | function p() { |
上面的代码用.catch()捕获了Promise
对象内模拟的异步请求失败的场景,执行了reject()函数。我们也可以指定.then()方法的第二个参数,也就是执行状态失败时的回调函数来匹配错误的情况,但是实际使用中不推荐这么写,推荐使用.catch()方法来捕获错误状态,同时指定需要执行的函数。
Promise.finally()
.finally()方法接收一个回调函数作为参数,该方法指定Promise
对象的状态结束时执行的回调函数。也就是说,不管此时状态为成功还是失败,只要从Pending状态变为了其它两种状态的任意一种,都会执行.finally()方法里的回调函数。finally()方法的回调函数不接受任何参数。
1 | function p() { |
上面的代码使用了.finally()方法,无论Promise
对象执行的是resolve
还是reject
,.finally()里的回调函数都会执行!
Promise.all()
在实际使用中,有这样一种情况。我们想监听多个Promise
对象的返回情况,如果所有的Promise
对象都返回成功状态,才执行成功的回调。.all()方法就是用来干这个的,它接收一个数组参数,数组里的每一项都是一个Promise
对象,都返回成功时.all()方法执行成功回调,如果有一项返回失败,则执行失败回调。这个方法的回调参数接收数组内每个Promise
对象返回的参数,并按照顺序将它们放到一个数组里。
1 | const p1 = new Promise((resolve, reject) => { |
上面的代码先创建里3个Promise
对象(省略了模拟异步请求的步骤,直接执行了resolve
方法),然后将这三个Promise
对象以数组的形式传递给.all()方法,同时每个Promise
对象里都分别传递了一个数字参数进去。.all()方法监听到三个Promise
对象状态均为成功,则执行.then()方法内的代码,打印出了success!。同时三个Promise
对象传递进来的数字参数也被接收到了。
如果.all()方法的参数里有状态为失败的对象,则.all()方法则执行失败回调,同时接收到的参数为第一个执行reject
函数时传递进来的参数。
1 | const p1 = new Promise((resolve, reject) => { |
Promise.race()
race译为赛跑,速度竞赛,顾名思义,这个方法就是返回最先改变状态的Promise
实例。直接看代码:
1 | const p1 = new Promise((resolve, reject) => { |
上面代码还是首先封装了三个Promise
对象,里面用setTimeout定时器模拟了异步请求,分别为1000ms,800ms和900ms,并分别传递了一个数字参数进去。可以看到最终是p2先执行完,所以打印出的是800ms的p2传递进去的参数5。