文章目录
- 需求
- 思路
- 代码
需求
可以一次发任意多个请求,如果有失败,则重发失败的请求,知道所有的都成功,或者超出最大重试次数,才返回最终结果。封装成可复用的并发请求工具。
实际的应用场景:数据太大,分批次传给后端,比如大文件分片上传
思路
使用循环、promise.all、promise.allSettled都可以用来发请求:
- 循环能做到并发,各发各的,各回各的,如果要使用循环,需要自己写一个监听者监听请求都完成,比较麻烦
- promise.all:一旦有一个请求失败,就会直接进入catch,一个失败,后面的请求都不发了,直接进入catch
- promise.allSettled:接收一组Promise实例,当所有输入的Promises都已确定其结果时,返回的Promise将被解决,其值是一个数组,每个元素对应于原始Promises数组中的一个位置,表示相应Promise的结果或原因。
把要发的请求写成一个方法,都交给
promise.allSettled
,拿到结果后,找到失败的请求,再次使用promise.allSettled重新发送
代码
<script setup>
//需求背景:并发多个异步操作,如果有失败的,则重试失败,直到所有请求成功,或者超出最大次数,就返回
//异步请求1
//f1,f2,f3为三个模拟的异步操作,模拟的请求
function f1(params) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(1, params)
reject(1)
}, 1000)
})
}
function f2() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(2)
resolve(2)
}, 1500)
})
}
function f3() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(3)
resolve(3);
}, 2000)
})
}
//构建工具类
class RequestList {
map = {};// 存储id和方法的对象关系
list = [];
success=[];
error=[];
constructor(fnlist, reTryTime = 3) {
this.list = fnlist;
fnlist.forEach((fn, index) => {
// 遍历方法,对每个方法进行包装:生成id、添加重发次数
let _id = "id_" + index;
//用id和方法,映射进map
this.map[_id] = fn;
//把id给到方法的静态属性
fn.id = _id;
fn.hasTry = 0;// 已经重发了多少次
fn.reTry = reTryTime;// 最大重发次数
})
}
createPromise(fn) {
// 包装响应
return new Promise((resolve, reject) => {
fn().then((res) => {
resolve({
id: fn.id,
value: res
})
}).catch((err) => {
reject({
id: fn.id,
value: err
})
})
})
}
send() {
return new Promise((resolve,reject)=>{
let _que = [];//待请求队列
this.list.forEach((fn) => {
_que.push(this.createPromise(fn));
})
//因为到时候失败了得再次调用allSetled重发,所以封装成一个方法,方便再次重发
const sendAllSettled = () => {
Promise.allSettled(_que).then((resList) => {
//发送完后,先清空
_que = []
resList.forEach((singres) => {
if (singres.status == 'fulfilled') {
this.success.push(singres.value.value)
} else {
//如果失败则找出原方法
let _id = singres.reason.id;
let _fn = this.map[_id];
//是否超出最大次数
if (_fn.hasTry < _fn.reTry) {
//如果出错了,则在加入待请求队列再次发送
_que.push(this.createPromise(_fn));
_fn.hasTry += 1;
} else {
this.error.push(singres.reason.value)
}
}
})
if (_que.length == 0) {
resolve({
success:this.success,
error:this.error
})
} else {
sendAllSettled()
}
})
}
sendAllSettled();
})
}
}
//使用
new RequestList([f1.bind(this, { a: 123 }), f2, f3], 5).send().then((res)=>{
console.log(res);
})
</script>
<template>
<div></div>
</template>
<style scoped>
header {
line-height: 1.5;
}
.logo {
display: block;
margin: 0 auto 2rem;
}
@media (min-width: 1024px) {
header {
display: flex;
place-items: center;
padding-right: calc(var(--section-gap) / 2);
}
.logo {
margin: 0 2rem 0 0;
}
header .wrapper {
display: flex;
place-items: flex-start;
flex-wrap: wrap;
}
}
</style>