Promise 方法及应用(all,race,allsettled,any)

8,062 阅读8分钟

书接上文,我们在上一篇的文章中提到了Promise的then、catch和finally方法,但是这些方法都是定义在Promise的原型上,而今天讲的这些方法是定义在Promise的实例上。

Promise.all()

我们先理解下字面的意思就是所有的Promise实例。下面我们看官方的解释:

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

是不是感觉上面的解释太简便了,以至于对它的用法有点模糊。那我们就详细地对Promise.all进行解释。

其实我们可以从参数和返回值两个方面对Promise.all()进行剖析。

首先是参数方面:

  • Promise.all()的参数是一个包含多个Promise实例的数组(当然数组内部Promise实例数量可以是一个,但那就没必要使用all方法,但会在你不清楚Promise实例数量时使用)。

然后看返回值方面。

返回值要分为两种情况:

  • 第一种情况就是数组内部的Promise实例全部成功,就是状态变为resolve,注意是全部,全部,全部(all)。这样它的返回值也是一个数组,但是这个数组里面的元素变为对应的原Promise实例的resolve状态的值。后面可以调用then方法使用。
  • 第二种情况就是数组内部的Promise实例没有全部成功,这样它的状态就会变为reject拒绝状态。

这样的话,我们就可以做一个简单地总结:

  • Promise.all()方法就是检测参数数组内部的所有Promise实例是否成功,若成功,则调用then方法进行处理,否则变为reject状态。

重点还是all,all,all。

源码

下面我们看下Promise.all对应的源码:

function myPromiseall(promises) {
    return new Promise(function(reslove, reject) {
        if (!Array.isArray(promises)) {
            throw new TypeError('argument must be a array')
        }
        var count = 0;
        var length = promises.length;
        var result = [];
        for (let i = 0; i < length; i++) {
            Promise.resolve(promises[i]).then(val => {
                count += 1;
                result[i] = val;
                if (count === length) {
                    return resolve(result)
                }
            }, err => {
                return reject(err)
            })
        }
    })
}

看完上面的再看源码是不是清晰多了。

下面我们结合几个例子加深印象。

// test
let p1 = new Promise(function(resolve, reject) {
    setTimeout(function() {
        resolve(1)
    }, 1000)
})
let p2 = new Promise(function(resolve, reject) {
    setTimeout(function() {
        resolve(2)
    }, 2000)
})
let p3 = new Promise(function(resolve, reject) {
    setTimeout(function() {
        resolve(3)
    }, 3000)
})
Promise.all([p3, p1, p2]).then(res => {
    console.log(res) // [3, 1, 2]
})

上面这个例子我们很好理解。

下面我们看一个不成功的例子。

let p1 = new Promise(function (resolve, reject) {
    setTimeout(function () {
        resolve(1)
    }, 1000)
})
let p2 = new Promise(function (resolve, reject) {
    setTimeout(function () {
        reject(2)
    }, 3000)
})
let p3 = new Promise(function (resolve, reject) {
    setTimeout(function () {
        reject(3)
    }, 10000)
})
Promise.all([p3, p1, p2]).then(res => {
    console.log(res)
}, err => {
    console.log(err);
})

然后再看下输出:

2
[Done] exited with code=0 in 10.197 seconds

从输出我们可以知道,Promise.all如果是不全成功,它会返回第一个reject状态的Promise实例。

Promise.race()

race是竞赛赛跑的意思,竞赛肯定最受关注的就是第一名,其他的就无所谓了。

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

const p = Promise.race([p1, p2, p3]);

上面代码中,只要p1p2p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。

看了第一节,第二节也就很容易看懂了。

源码

直接上源码:

Promise.race = function(args) {
    return new Promise((resolve, reject) => {
        for (let i = 0, len = args.length; i < len; i++) {
            args[i].then(resolve, reject)
        }
    })
}

哇瑟,源码是不是很简单。确实很简单,就是返回第一个状态改变的Promise实例。

直接上例子:

如果指定时间内没有获得结果,就将 Promise 的状态变为reject,否则变为resolve

const p = Promise.race([
  fetch('/resource-that-may-take-a-while'),
  new Promise(function (resolve, reject) {
    setTimeout(() => reject(new Error('request timeout')), 5000)
  })
]);
​
p
.then(console.log)
.catch(console.error);

这也是fetch进行超时设置的原理。

再看个简单的例子:

let p1 = new Promise(function (resolve, reject) {
    setTimeout(function () {
        resolve(1)
    }, 1000)
})
let p2 = new Promise(function (resolve, reject) {
    setTimeout(function () {
        reject(2)
    }, 3000)
})
let p3 = new Promise(function (resolve, reject) {
    setTimeout(function () {
        reject(3)
    }, 2000)
})
Promise.race([p3, p1, p2]).then(res => {
    console.log(res)//1
}, err => {
    console.log(err);
})
let p1 = new Promise(function (resolve, reject) {
    setTimeout(function () {
        resolve(1)
    }, 1000)
})
let p2 = new Promise(function (resolve, reject) {
    setTimeout(function () {
        reject(2)
    }, 300)
})
let p3 = new Promise(function (resolve, reject) {
    setTimeout(function () {
        reject(3)
    }, 2000)
})
Promise.race([p3, p1, p2]).then(res => {
    console.log(res)
}, err => {
    console.log(err);//2
})

race就看那个Promise实例快。

Promise.allSettled()

  • Promise.all方法适用于一组异步操作全部成功后调用或者这组操作中的一个失败后调用
  • Promise.race方法适用于一组异步操作第一个成功或者失败后调用

但如果我们想等这组异步操作全部结束后再调用,上面的方法就有点捉襟见肘了。这样我们引出一个新的方法来实现我们的需求,就是Promise.allSettled()

  • Promise.allSettled()方法接受一个数组作为参数,数组的每个成员都是一个 Promise 对象,并返回一个新的 Promise 对象。只有等到参数数组的所有 Promise 对象都发生状态变更(不管是fulfilled还是rejected),返回的 Promise 对象才会发生状态变更。

看个例子加深印象:

const promises = [
  fetch('/api-1'),
  fetch('/api-2'),
  fetch('/api-3'),
];
​
await Promise.allSettled(promises);
removeLoadingIndicator();

上面示例中,数组promises包含了三个请求,只有等到这三个请求都结束了(不管请求成功还是失败),removeLoadingIndicator()才会执行

该方法返回的新的 Promise 实例,一旦发生状态变更,状态总是fulfilled,不会变成rejected。状态变成fulfilled后,它的回调函数会接收到一个数组作为参数,该数组的每个成员对应前面数组的每个 Promise 对象。

const resolved = Promise.resolve(42);
const rejected = Promise.reject(-1);
​
const allSettledPromise = Promise.allSettled([resolved, rejected]);
​
allSettledPromise.then(function (results) {
  console.log(results);
});
// [
//    { status: 'fulfilled', value: 42 },
//    { status: 'rejected', reason: -1 }
// ]

上面代码中,Promise.allSettled()的返回值allSettledPromise,状态只可能变成fulfilled。它的回调函数接收到的参数是数组results。该数组的每个成员都是一个对象,对应传入Promise.allSettled()的数组里面的两个 Promise 对象。

这样的话,我们是不是就可以通过Promise.allSettled()方法来知道数组内部的所有Promise实例最后的状态。

源码

这样我们看下源码:

function allSettled(promises) {
 
  return new Promise((resolve, reject) => {
   if (!Array.isArray(promises)) {
        throw new TypeError('argument must be a array')
    }
     
    const result = []
    promises.forEach((promise, index) => {
      promise.then((value) => {
        result[index] = {
          status: 'fulfilled',
          value
        }
      }, (reason) => {
        result[index] = {
          status: 'rejected',
          reason
        }
      })
    })
  })
}

Promise.any()

Promise.any()主要是针对只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。

同时还有一个特点:

Promise.any()不会因为某个 Promise 变成rejected状态而结束,必须等到所有参数 Promise 变成rejected状态才会结束。

看下返回值:

  • 只要其中的一个 promise 成功,就返回那个已经成功的 promise
  • 如果可迭代对象中没有一个 promise 成功(即所有的 promises 都失败/拒绝),就返回一个失败的 promiseAggregateError 类型的实例,它是 Error 的一个子类,用于把单一的错误集合在一起

源码

MyPromise.any = function(promises){
  return new Promise((resolve,reject)=>{
    promises = Array.isArray(promises) ? promises : []
    let len = promises.length
    // 用于收集所有 reject 
    let errs = []
    // 如果传入的是一个空数组,那么就直接返回 AggregateError
    if(len === 0) return reject(new AggregateError('All promises were rejected'))
    promises.forEach((promise)=>{
      promise.then(value=>{
        resolve(value)
      },err=>{
        len--
        errs.push(err)
        if(len === 0){
          reject(new AggregateError(errs))
        }
      })
    })
  })
}

下面看几个简单的例子:

let p1 = new Promise(function (resolve, reject) {
    setTimeout(function () {
        resolve(1)
    }, 1000)
})
let p2 = new Promise(function (resolve, reject) {
    setTimeout(function () {
        resolve(2)
    }, 300)
})
let p3 = new Promise(function (resolve, reject) {
    setTimeout(function () {
        reject(3)
    }, 2000)
})
​
Promise.any([p1, p2, p3]).then(res => {
    console.log(res)//2
})

上面的Promise数组中有两个Promise实例状态是成功的,但any只返回最先成功的一个,这个和Promise.race方法有点类似。

那如果是全部失败呢?

let p1 = new Promise(function (resolve, reject) {
    setTimeout(function () {
        reject(1)
    }, 1000)
})
let p2 = new Promise(function (resolve, reject) {
    setTimeout(function () {
        reject(2)
    }, 300)
})
let p3 = new Promise(function (resolve, reject) {
    setTimeout(function () {
        reject(3)
    }, 2000)
})
​
Promise.any([p1, p2, p3]).then(res => {
    console.log(res)
}).catch(err => {
    console.log(err);
})

看下结果:

AggregateError: All promises were rejected

总结

相同点这四种方法的参数都是包含Promise实例的数组。

看下不同点:

作用:

  • Promise.all() 全成功我成功 失败一个我失败
  • Promise.race() 谁第一个改变状态就是谁的,无论成功或失败
  • Promise.allSettled() 管你成功或失败,全部都得运行完
  • Promise.any() 一个成功我成功,全部失败我失败

状态成功时返回值:

  • Promise.all() 返回状态成功时的数组
  • Promise.race() 第一个成功的
  • Promise.allSettled() 无所谓成功或失败,返回值都是一个包含状态对象的数组
  • Promise.any() 返回第一个成功的

状态失败时返回值:

  • Promise.all() 第一个失败的
  • Promise.race() 第一个失败的
  • Promise.allSettled() 无所谓成功或失败,返回值都是一个包含状态对象的数组
  • Promise.any() AggregateError: All promises were rejected

是否将参数数组内部的Promise实例全部执行完,才调用

  • Promise.all() 成功是是,失败是否
  • Promise.race() 不是
  • Promise.allSettled() 是
  • Promise.any() 成功是否,失败是是

引用

Promise.any 的作用,如何自己实现一个 Promise.any

Promise 对象