并发即在同一时间段内执行多个任务或者处理多个任务,这有什么用呢?比如在前端一些大文件的分片上传,如果分片后上传完成一个之后再上传,那么效率就会比较低,但是如果不限制,一次性都发送,那么又会太大,所以我们需要控制一下发送的数量,也就是在一次发送的队列中,最多允许多少个任务一起执行
准备数据
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)
})