JavaScript实现并发请求
2023-12-28 16:39:33
并发即在同一时间段内执行多个任务或者处理多个任务,这有什么用呢?比如在前端一些大文件的分片上传,如果分片后上传完成一个之后再上传,那么效率就会比较低,但是如果不限制,一次性都发送,那么又会太大,所以我们需要控制一下发送的数量,也就是在一次发送的队列中,最多允许多少个任务一起执行
思路分析
- 任务定义:这个任务的结果只会成功,无论这个任务的结果是成功还是失败,都算做是成功的状态
- 我们假设存在20个任务,并发数量为5,如果最大数量为5,是不是表示在第一次发送请求的时候,我们需要发送5个这样的任务
- 需要任务的话,那么首先我们就需要有这个任务,也就是说我们要封装一个异步请求函数,而每次并发执行的任务其实就是调用一次这个函数
- 那当最开始发送的5个任务中当其中某一个完成之后,又应该进行怎么样的操作呢?比如第二个任务完成了,其他四个任务还在进行中,此时任务队列的任务数量为4,那么我们就要在这个任务执行完成的时候去调用下一个任务
- 那怎么调用下一个任务呢?上传的是分片的文件数据,那么就会有一个存储这个分片数据的数组吧,数组怎么取值呢?是不是通过索引就可以呢?所以当第二个任务完成的时候,只需要通过 arr[i] 就可以获取下一个分片的数据了,并利用我们封装好的函数将其包装为一个任务,进行发送
- 所以为了得到这个 i 的值,那么每当封装的任务进入发送队列之后,就需要将索引+1,从而取下一个任务,而有了这个执行逻辑之后,我们就可以实现一个维持固定数量执行的任务队列了
- 比如最开始的发送了5个任务,也就意味着索引自增了5次,那么第二个任务完成了,又因为索引的自增,就可以顺序的取到下一个任务,将这个任务进入到一开始第二个的任务队列位置进行发送,在这个基础上不停的循环,从而实现并发请求
具体实现
-
准备数据
const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
-
这里就简单的用一些数字了,封装一个函数,将数据包裹为一个异步请求任务
function uploadData(value) { return new Promise((resolve, reject) => { // 模拟请求 setTimeout(() => { resolve(`数据【${value}】执行完成`) }, 1000) }) }
-
现在就要开发编写真正实现并发请求的方法了,这个方法,根据分析,应该接收两个参数,数据和最大数量,返回是一个Promise对象,当所有请求完成之后执行成功,如下:
function concurrentRequests(list, maxNum) { return new Promise(resolve => {}) }
-
再次基础上,我们还需要什么,是不是需要定义数组来接收每个任务执行完成后的结果呢,成功和失败都放入进来
function concurrentRequests(list, maxNum) { const result = [] return new Promise(resolve => { }) }
-
在这个函数内部,我们还需要一个函数来帮助我们取数据,并调用 uploadData 发送任务,并或结果存入 result 数组
function concurrentRequests(list, maxNum) { const result = [] let index = 0 return new Promise(resolve => { // 定义函数 async function request() { // 保存索引-可以保证存储的结果和原始数据数组的顺序一致 const i = index // 调用一次之后索引自增 index++ try { // 接受成功的结果 const resp = await uploadData(list[i]) // 利用保存的索引存入结果数组 result[i] = resp } catch (error) { // 失败的结果也保存 request[i] = error } finally { // 无论成功或失败,在在此处执行,重新调用 request 方法 // - 同时保证不会取到空的数据发送 if (index < list.length) { request() } } } }) }
-
现在就应该根据最开始的 maxNum 来决定发送时的数量
function concurrentRequests(list, maxNum) { const result = [] let index = 0 return new Promise(resolve => { async function request() { const i = index index++ try { const resp = await uploadData(list[i]) result[i] = resp } catch (error) { request[i] = error } finally { if (index < list.length) { request() } } } // 使用循环来达到队列发送任务数量 for (let i = 0; i < maxNum; i++) { request() } }) }
-
为什么使用循环可以呢,这样不是还是一次一次的发送吗,这是因为在循环中是同步的,而任务是异步的,所以就说只有当同步任务执行完成之后才会执行异步
-
那什么时候才算成功呢?是直接判断当索引等于数组长度-1吗?那肯定不行,当最后一个任务完成的时候,此时索引已经满足数组长度-1了,但是你无法保证和他一个在一个队列的任务已经完成了,所以我们还要定义一个变量,来确定是否完成
const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20] function uploadData(value) { return new Promise((resolve, reject) => { // 模拟请求 setTimeout(() => { resolve(`数据【${value}】执行完成`) console.log(`上传数据【${value}】完成`) }, getRandomInt(1, 3) * 1000) }) } // 生成随机数 function getRandomInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min } function concurrentRequests(list, maxNum) { const result = [] // 结果数组 let index = 0 // 索引 let count = 0 // 完成的任务计数 return new Promise(resolve => { async function request() { const i = index index++ try { const resp = await uploadData(list[i]) result[i] = resp } catch (error) { request[i] = error } finally { // 计数+1 count++ if (count === list.length) { resolve(result) } if (index < list.length) { request() } } } for (let i = 0; i < maxNum; i++) { request() } }) } concurrentRequests(data, 5).then(res => { console.log(res) })
-
剩下的就是一些细节的判断了
const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20] function uploadData(value) { return new Promise((resolve, reject) => { // 模拟请求 setTimeout(() => { resolve(`数据【${value}】执行完成`) console.log(`上传数据【${value}】完成`) }, getRandomInt(1, 3) * 1000) }) } // 生成随机数 function getRandomInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min } function concurrentRequests(list, maxNum) { const result = [] // 结果数组 let index = 0 // 索引 let count = 0 // 完成的任务计数 return new Promise(resolve => { async function request() { if (!list.length) { // 数组没有数组时返回空数组 resolve([]) } // 保存索引-可以保证存储的结果和原始数据数组的顺序一致 const i = index // 调用一次之后索引自增 index++ try { // 接受成功的结果 const resp = await uploadData(list[i]) // 利用保存的索引存入结果数组 result[i] = resp } catch (error) { // 失败的结果也保存 request[i] = error } finally { // 计数+1 count++ if (count === list.length) { resolve(result) } // 无论成功或失败,在在此处执行,重新调用 request 方法 // - 同时保证不会取到空的数据发送 if (index < list.length) { request() } } } // 当数组长度小于并发数时 for (let i = 0; i < Math.min(list.length, maxNum); i++) { request() } }) } concurrentRequests(data, 5).then(res => { console.log(res) })
-
现在增加一些输出语句,查看结果
function uploadData(value) { console.log(`start:数据【${value}】...`) return new Promise((resolve, reject) => { // 模拟请求 setTimeout(() => { resolve(`数据【${value}】执行完成`) console.log(`end:数据【${value}】`) }, getRandomInt(1, 3) * 1000) }) } concurrentRequests(data, 5).then(res => { console.log(res) })
-
结果如图:
-
可以看到,因为随机的原因,导致执行完成的顺序虽然不一致,但是还是保证了结果与数据源的顺序一致
源码展示
const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
// 模拟上传数据方法
function uploadData(value) {
console.log(`start:数据【${value}】...`)
return new Promise((resolve, reject) => {
// 模拟请求
setTimeout(() => {
resolve(`数据【${value}】执行完成`)
console.log(`end:数据【${value}】`)
}, getRandomInt(1, 3) * 1000)
})
}
// 生成随机数
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min
}
function concurrentRequests(list, maxNum) {
const result = [] // 结果数组
let index = 0 // 索引
let count = 0 // 完成的任务计数
return new Promise(resolve => {
async function request() {
if (!list.length) {
// 数组没有数组时返回空数组
resolve([])
}
// 保存索引-可以保证存储的结果和原始数据数组的顺序一致
const i = index
// 调用一次之后索引自增
index++
try {
// 接受成功的结果
const resp = await uploadData(list[i])
// 利用保存的索引存入结果数组
result[i] = resp
} catch (error) {
// 失败的结果也保存
request[i] = error
} finally {
// 计数+1
count++
if (count === list.length) {
resolve(result)
}
// 无论成功或失败,在在此处执行,重新调用 request 方法
// - 同时保证不会取到空的数据发送
if (index < list.length) {
request()
}
}
}
// 当数组长度小于并发数时
for (let i = 0; i < Math.min(list.length, maxNum); i++) {
request()
}
})
}
concurrentRequests(data, 5).then(res => {
console.log(res)
})
文章来源:https://blog.csdn.net/qq_53109172/article/details/135242983
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!