在工作中当我们的项目来到一个新的页面需要发多个请求,而这些请求的数据又毫不相干时,我们可以采取并发请求的方式。目前并发请求主要有Promise.all和axios.all两种方式,下面做详细介绍。
Promise.all 方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
var p = Promise.all([p1,p2,p3]);
上面代码中,Promise.all 方法接受一个数组作为参数,p1、p2、p3 都是 Promise 对象的实例。(Promise.all 方法的参数不一定是数组,但是必须具有 iterator 接口,且返回的每个成员都是 Promise 实例。)
p 的状态由 p1、p2、p3 决定,分成两种情况
下面是一个具体的例子
// 3个函数都是封装的axios,返回promise实例的函数
let reqArr = [getNames(), getTypes(), getAges()]
Promise.all(reqArr).then((resArr) => {
console.log('请求结果', resArr)
})
// 如果3个请求都成功,返回结果是存放3个请求结果的数组
axios.all的用法基本和Promise.all一致,因为axios.all方法就是对Promise.all方法进行了一层包装,本质上是一模一样的,没有任何额外的逻辑,所以调用axios.all方法就是调用了Promise.all方法。
注意:axios.all是axios的静态方法,不是axios实例的方法!可通过在main.js中引入axios,并将其挂载在vue原型上,实现全局使用
let reqArr = [getNames(), getTypes(), getAges()]
axios.all(reqArr).then((resArr) => {
console.log('请求结果', resArr)
})
// 返回结果同上
axios.all与Promise.all有区别的地方在于then中对于返回结果的处理,axios除了上面这种用法之外,还可以在then中调用axios.spread函数,axios.spread函数接受一个回调函数,回调函数的参数就是与请求相同顺序和数量的返回结果
let reqArr = [getNames(), getTypes(), getAges()]
axios.all(reqArr).then(axios.spread((first, second, third)=>{
console.log('结果1', first)
console.log('结果2', second)
console.log('结果3', third)
}))
返回结果
axios.all与Promise.all之间的关系
axios.all方法就是对Promise.all方法进行了一层包装,本质上是一模一样的,没有任何额外的逻辑
axios.all = function(promises) {
return Promise.all(promises);
};
// axios.all的then
axios.spread((first, second) => {})
// Promise.all的then
([first, second]) => {}
我们可以看到,Promise.all的then方法里面是个函数,函数的参数是所有请求的响应组成的数组;而axios.all的then方法里面调用了axios.spread方法,axios.spread方法接收一个函数作为参数,该参数函数的参数也是所有请求的响应,既然上文说了axios.all方法与Promise.all方法是一模一样的,那么我们只需想办法再让两个then方法相同即可。也就是说我们创建一个axios.spread方法并且让axios.spread((first, second) => {})的返回值与([first, second]) => {}等价即可。
axios.spread的实现
axios.all = function(promises) {
return Promise.all(promises);
};
axios.spread = function(callback) {
return function wrap(arr) {
return callback.apply(null, arr);
};
};
根据Promise.all的用法我们知道,当参数数组中的所有promise实例都是fulfilled状态时,Promise.all的返回实例才会是fulfilled,否则返回实例的状态就是rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
这也就意味着我们并发的几个请求当中,只要有一个请求出错,就无法返回正常的结果。但我们希望有错误出现时,也能正常返回其他请求成功的结果。我们可以通过为每个请求添加catch错误处理来实现
let reqArr = [axios({method: 'get', url: 'www.baidu.com'}), getTypes(), getAges()]
// 为每个请求添加错误处理
let handledErrReqs = reqArr.map(item => item.catch(error => {
// 错误处理代码
console.log('请求出错', error)
// 也可以返回你想要的错误提示结果,如果不返回数据,那么获得的值就是undefined
// return {msg: '请求出错'}
}))
axios.all(handledErrReqs).then(axios.spread((first, second, third)=>{
console.log('结果1', first)
console.log('结果2', second)
console.log('结果3', third)
}))
以上方法对axios.all与Promise.all都适用
如果我们想获得reqArr 中的所有数据,不管他是成功还是失败
它的用法和Promise.all一致,只是返回结果有所差异
Promise.allSettled 等待所有的 promise 都被 settle,无论结果如何。结果数组具有:
例如对于上面的例子
let reqArr = [axios({method: 'get', url: 'www.baidu.com'}), getTypes(), getAges()]
Promise.allSettled(reqArr).then(results => {
console.log(results )
})
上面的results将会是
[
{status: 'rejected', reason: ...error object...},
{status: 'fulfilled', value: ...response...},
{status: 'fulfilled', value: ...response...}
]
所以,对于每个 promise,我们都得到了其状态(status)和 value/reason。
注意:这个方法是最近新增的内容,所以存在一些兼容性问题,可通过polyfill解决。
如果浏览器不支持 Promise.allSettled,很容易进行 polyfill:
if (!Promise.allSettled) {
const rejectHandler = reason => ({ status: 'rejected', reason });
const resolveHandler = value => ({ status: 'fulfilled', value });
Promise.allSettled = function (promises) {
const convertedPromises = promises.map(p => Promise.resolve(p).then(resolveHandler, rejectHandler));
return Promise.all(convertedPromises);
};
}
在这段代码中,promises.map 获取输入值,并通过 p => Promise.resolve§ 将输入值转换为 promise(以防传递了 non-promise),然后向每一个 promise 都添加 .then 处理程序(handler)。
这个处理程序(handler)将成功的结果 value 转换为 {status:‘fulfilled’, value},将 error reason 转换为 {status:‘rejected’, reason}。这正是 Promise.allSettled 的格式。
然后我们就可以使用 Promise.allSettled 来获取 所有 给定的 promise 的结果,即使其中一些被 reject。
参考文章:
【异步技术】Axios并发请求
axios添加axios.all和axios.spread方法,与promise.all
并发请求时错误处理
JavaScript Promise 对象