近期在项目开发中,遇到并发上报信息的问题。由于业务需要,要求同一时间将数组中的数据一起并且逐条发送给后台处理;在使用Promise.all处理多个异步任务时,发现1毫秒间十几条异步请求就发送完毕,导致服务器无法同时处理完毕这些任务,最终部分请求失败问题。
所以在使用Promise.all的同时,也需要将每个异步任务的发送时间作个延迟处理。
一、了解Promise拓展方法
在实现延迟效果前,先了解下Pomise实例的拓展方法,如下图:
序号 | 名称 | 描述 |
---|---|---|
1 | Promise.all() | 所有的Promise对象均成功之后,才会执行then回调函数,否则执行catch回调函数。 |
2 | Promise.allSettled() | 确认一组异步操作是否都结束了(不管成功或失败),包含了”fulfilled"和"rejected"两种情况。 |
3 | Promise.any() | ES2021引入了Promise.any()方法,其和all相反,所有的Promise对象均失败后才会执行catch回调函数,否则当任意一个异步任务成功后就会执行then回调函数。 |
4 | Promise.race() | 同样是多个Promise实例,不过一旦某个任务完成或失败,则会执行then回调函数或catch回调函数,并返回最先完成任务的响应结果。 |
1.1、Promise.all()示例
// 循环生成10条异步任务(模拟发送请求)
const requestList = Array.from({length: 10}).map((val, index) => {
return new Promise((resolve, reject) => {
// 最后一条发送失败结果
index == 9 ? reject('error msg') : setTimeout(resolve, 1000, index);
});
});
Promise.all(requestList).then(res => {
console.log(res);
}).catch(msg => {
console.error(msg);
});
运行结果可看出,只要其中一个任务失败,则执行catch回调函数。全部任务执行成功才会执行then回调函数。如下图:
1.2、Promise.allSettled()示例
// 循环生成10条异步任务(模拟发送请求)
const requestList = Array.from({length: 10}).map((val, index) => {
return new Promise((resolve, reject) => {
// 最后一条发送失败结果
index == 9 ? reject('error msg') : setTimeout(resolve, 1000, index);
});
});
Promise.allSettled(requestList).then(res => {
console.log(res);
}).catch(msg => {
console.error(msg);
});
从结果上可以看出,不管成功或失败,其都会执行then回调,并且成功的status状态值为fulfilled,失败的status状态值为rejected。如下图:
1.3、Promise.any()示例
// 循环生成10条异步任务(模拟发送请求)
const requestList = Array.from({length: 10}).map((val, index) => {
return new Promise((resolve, reject) => {
// 最后一条发送失败结果
index == 9 ? reject('error msg') : setTimeout(resolve, 1000, index);
});
});
Promise.any(requestList).then(res => {
console.log(res);
}).catch(msg => {
console.error(msg);
});
其结果可见,只返回了一个成功的结果为0,从而得出结论:不管成功或失败,哪个任务最先执行完成,并会执行其回调结果。如下图:
1.4、Promise.race()示例
// 循环生成10条异步任务(模拟发送请求)
const requestList = Array.from({length: 10}).map((val, index) => {
return new Promise((resolve, reject) => {
// 最后一条发送失败结果
index == 6 ? reject('error msg') : setTimeout(resolve, 1000, index);
});
});
Promise.race(requestList).then(res => {
console.log(res);
}).catch(msg => {
console.error(msg);
});
从结果中可见,多条异步任务中,只要有一个失败,则会执行catch回调函数。如下图:
1.5、any与race的相同点和区别
相同点:
- 都是返回最先处理完毕的任务响应结果。
区别:
- 二者都是返回最先执行完毕的任务的响应结果。
- any是不管成功或失败,只返回第一个执行完毕的结果;race则是有一个任务失败,只会返回失败的响应结果。
- any是等所有任务完毕后执行then或catch回调函数;race只要遇到失败任务,则会结束并执行catch回调函数。
1.6、race与all的相同点和区别
相同点:只要有任务失败,则执行catch回调函数。
区别:all全部成功,返回是所有任务的响应结果(数组形式);race只返回第一个完成任务的响应结果。
二、异步任务延迟处理
在了解Promise的四种拓展方法后,根据本项目需求,则选用Promise.all()来完成所有的异步任务同时发送,并且对每个异步任务做延时发送处理。
2.1 引入api函数
对于Axios的使用,这里不作细说,对axios的封装不清楚的朋友可以看下之前写过的一篇,地址:Vue.js快速入门之四:axios安装和使用_安装axios-CSDN博客
在vue项目中,创建api/index.js用于定义接口请求函数,request.js为axios的封装文件。代码如下:
import Service from '@/utils/request'
export const sendMsg = ({text}) => {
return Service({
url: "/addCommon",
method: 'post',
params: {
text
}
})
}
页面中引入,代码如下:
<template>
<div>
<el-button type="info" size="small" style="margin-bottom: 20px;" @click="sendMsgEvent2">添加</el-button>
<el-input type="textarea" rows="10" placeholder="请输入内容" v-model="commentText"></el-input>
</div>
</template>
<script>
import { sendMsg } from '@/api/index'
export default {
}
2.2 同时发送异步请求
<template>
<div>
<el-button type="info" size="small" style="margin-bottom: 20px;" @click="sendMsgEvent">发送</el-button>
<el-input type="textarea" rows="10" placeholder="请输入内容" v-model="commentText"></el-input>
</div>
</template>
<script>
import { sendMsg } from '@/api/index'
export default {
data(){
return {
commentText: ""
}
},
methods: {
sendMsgEvent(){
if(this.commentText.trim() == '') {
this.$message({type: "info", message: "请输入内容"});
return;
}
let startTime = Date.now();
// 循环追加共10个异步请求
const reqs = Array.from({length: 10}).map(() => {
return sendMsg({text: this.commentText, startTime})
});
// 通过Promise.all发送请求
Promise.all(reqs).then(res => {
console.log('ok', res);
}).catch(e => {
console.error('error', e);
})
}
// end
}
}
</script>
start为所有异步任务发送时间,这么多条数据同时发送到服务端,会导致服务器负载增加,影响性能,可能导致服务器卡死或响应缓慢,造成部分请求失败等问题。结果如下图:
2.3 定义延迟执行函数
所以既要满足业务需要,也要减轻服务端压力,则需要将多条异步任务进行延迟发送。在将数组中任务给到Promise.all之前,对应时行再加工。将每个异步任务,都依次序往后延迟100毫秒。代码如下:
export const delaySend = ({request, data}, index) => {
return new Promise((resolve, reject) => {
setTimeout(e => {
// 追加开始时间,用于展示发送时的时间,实际项目中可去除
data['startTime'] = Date.now();
// 执行接口函数request,并将入参带入
request(e).then(resolve).catch(reject);
}, 100 * index, data);
})
}
页面代码如下:
<template>
<div>
<el-button type="info" size="small" style="margin-bottom: 20px;" @click="sendMsgEvent">发送</el-button>
<el-input type="textarea" rows="10" placeholder="请输入内容" v-model="commentText"></el-input>
</div>
</template>
<script>
import { sendMsg } from '@/api/index'
import { delaySend } from '@/utils/utils'
export default {
data(){
return {
commentText: ""
}
},
methods: {
sendMsgEvent(){
if(this.commentText.trim() == '') {
this.$message({type: "info", message: "请输入内容"});
return;
}
// 循环追加共10个异步请求
const reqs = Array.from({length: 10}).map(() => {
return {request: sendMsg, data: {text: this.commentText}}
}).map((item, index) => delaySend(item, index));
// 通过Promise.all批量发送
Promise.all(reqs).then(res => {
console.log('ok', res);
}).catch(e => {
console.error('error', e);
})
}
// end
}
}
</script>
这次结果可以看出,每个任务的开始发送时间,都是不一样的,基本都相差上百毫秒。这样服务器即可减轻服务端的压力,也能让所有请求全部得到响应,不会出现请求无响应问题了。结果如下图:
注:diff为接口响应时间,结束时间和开始之差,即end-start=diff。