Promise的链式调用和axios处理高并发
最近在项目中又遇到了一个接口的请求需要依赖另一个接口的请求结果和处理高并发的场景了,所以即兴在这里简单总结和分享下,欢迎指正和补充。
一、Promise 简要回顾
Promise 是一个构造函数,作为 ES6 中最重要的特性之一,它有 all、resolve、reject、race ... 眼熟的方法,原型上有 then、catch 等同样熟悉的方法,所以 new Promise 返回的 promise 对象也有 then、catch 方法。
我们知道 Promise 能帮助我们避免回调地狱,可以理解成 Promise 就是对于回调函数的另一种写法。虽然从表面上看,Promise 能够简化层层回调的写法,但 Promise 通过传递状态的方式来使得回调函数能够及时调用,它比传统的回调函数和事件更简单、更灵活。
以下以定时器模拟,对于多个请求情况的处理,实例化如下:
function getP1() { console.log('getP1开始调用') return new Promise((resolve, reject) =>{ setTimeout(function(){ console.log('getP1调用完成'); resolve('getP1调用成功'); }, 1000); }); } function getP2() { console.log('getP2开始调用') return new Promise((resolve, reject) => { setTimeout(function(){ console.log('getP2调用完成'); resolve('getP2调用成功'); }, 2000); }); } function getP3() { console.log('getP3开始调用') return new Promise((resolve, reject) => { setTimeout(function(){ console.log('getP3调用完成'); resolve('getP3调用成功'); }, 3000); }); }
二、使用场景 ①
仅调用,不考虑其他,只要执行了即可。
getP1().then(res1 => { console.log('getP1结果:' + res1) })
getP2().then(res2 => { console.log('getP2结果:'+ res2) }) ......
三、使用场景 ②
先调用 p1,再调用 p2,再调用 p3 ......
getP1() .then(res1 => { console.log('getP1结果:' + res1) return getP2() }) .then(res2 => { console.log('getP2结果:' + res2) return getP3() }) .then(res3 => { console.log('getP3结果:' + res3) })
运行结果如下:
从结果可以看出,像这样按顺序,每隔一秒输出每个异步回调中的内容,就是链式调用。具体代码执行顺序:
1)第一步调用 getP1 函数,执行完成返回一个 promise,状态是成功,即 resolve('getP1调用成功')
,然后将参数 "getP1调用成功" 传递并执行第一个 then;
2)第一个 then 接收 "getP1调用成功",执行 then 的回调,回调中调用了 getP2,执行并返回成功状态的 promise,然后给下一个 then 传递参数 "getP2调用成功";
3)第二个 then 接收 "getP2调用成功",执行 then 的回调,回调中调用了 getP3,执行并返回成功状态的 promise,然后给下一个 then 传递参数 "getP3调用成功";
4)最后一个 then 接收 "getP3调用成功",执行回调,然后输出 "getP3调用成功"。
注意:
-
-
如果需要满足链式调用,.then 方法中必须返回一个 promise 对象。
-
.then 在链式调用时,会等其前一个 then 中的回调函数执行完毕,并且返回成功状态的 promise,才会执行下一个 then 的回调函数,而且 .then 方法的参数就是上一个 .then 方法中 resolve 的参数。
所以链式调用比较常用的一个场景就是,当下一个操作依赖于上一个操作的结果或状态的时候,可以很方便地通过 .then 方法的参数来传递数据。
-
四、reject 的使用
前面的例子只有执行成功的回调,没有失败的情况,可以通过 reject 把 Promise 的状态置为 rejected(代码抛出异常或出错了),然后在 .catch 方法中捕捉到执行失败情况的回调。例如:
function getP2() { console.log('getP2开始调用') return new Promise((resolve, reject) =>{ setTimeout(function(){ console.log('getP2调用完成'); // 这里偷懒直接这样写了,可以添加一些处理判断逻辑等 reject('getP2调用失败'); }, 2000); }); } getP1() .then(res1 => { console.log('getP1结果:' + res1) return getP2() }) .then(res2 => { console.log('getP2结果:' + res2) return getP3() }) .then(res3 => { console.log('getP3结果:' + res3) }) .catch(err => { console.log('error:' + err) })
执行结果如下:
每一个.then 都是衔接着上一个 promise 的,.catch 会捕捉任意一个 promise 的 reject 状态。当 .then 返回一个 rejected 的 promise 时,后面就不执行了,抛出的错误也会被 .catch 方法捕获。
五、使用场景 ③
p3 的调用依赖 p1 和 p2 的执行结果
这就用到了 Promise 的 all 方法,它提供了并行执行异步操作的能力,并且在所有异步操作执行完后才执行回调。
Promise.all 接收一个 promise 对象的数组作为参数,当这个数组里的所有 promise 对象全部变为 resolve 或 reject 状态的时候,它才会去调用 .then 方法。看下面的例子(此处仍使用上面定义好的getP1、getP2、getP3 这三个函数):
Promise .all([getP1(), getP2(), getP3()]) .then(res => { // 返回的 res 由 getP1,getP2,getP3 返回的结果所构成的数组 console.log(res); });
上面代码的输出结果如下:
有了 all 方法,我们就可以并行执行多个异步操作,并且在一个回调中处理所有的返回数据,就是上面的 res。比如打开网页时,需要预先加载用到的资源如图片或静态文件,等所有的都加载完后,我们再进行页面的初始化。
如果把 getP2 的 Promise 状态设置为 reject,执行如下代码:
Promise.all([getP1(), getP2(), getP3()])
.then(res => {
console.log('all:' + res)
})
.catch(err => {
console.log('err: ' + err)
})
执行结果如下:
注意:
-
getP1,getP2,getP3 的状态都是 resolve 的时候,Promise.all 的状态才会变成 resolve;
-
getP1,getP2,getP3 中只要有一个的状态为 reject,Promise.all 的状态就会变成 reject,后面的就不会再执行了;
-
回调函数中的返回数据,和传递的 promise 数组的顺序是一致的。
六、Promise.all() 方法的使用情景:
-
p3 依赖 p1 和 p2,p1 和 p2 之间异步执行
Promise.all([getP1(), getP2()]) .then(([res1, res2]) => { console.log('res1:' + res1) console.log('res2:' + res2) return getP3() }) .then(res => { console.log(res) })
结果如下:
-
p3 依赖 p1 和 p2,p1 和 p2 同步执行
Promise.all([await getP1(), await getP2()]) .then(([res1, res2]) => { console.log('res1:' + res1) console.log('res2:' + res2) return getP3() }) .then(res => { console.log(res) })
结果:
七、axios 处理高并发
相似的,在官方 axios 中,提供了 axios.all() 和 axios.spread() 两个函数,用于处理同时发送多个并发请求,在多个请求都完成后再执行一些逻辑。此处借用官方文档的示例:
function getUserAccount() { return axios.get('/user/12345'); } function getUserPermissions() { return axios.get('/user/12345/permissions'); } axios.all([getUserAccount(), getUserPermissions()]) .then( axios.spread((acct, perms) => { // 两个请求都完成后 // acct -> getUserAccount 的返回值 // perms -> getUserPermissions 的返回值 }) );
注意:
-
- axios.all() 方法接收一个数组作为参数,数组的每个元素都是一个请求,返回一个 promise 对象;
-
当数组中所有请求执行完成后,才执行 axios.spread() 中的函数,且 axios.spread() 回调函数中返回值的顺序和请求的顺序一致。
可以看到 axios.all() 方法与 Promise.all() 方法,不管在使用方式还是传参形式都是一模一样的。