在JavaScript中,我们经常需要处理并发请求,但有时我们希望限制并发请求数,以避免过多的请求同时发送到服务器。在这篇文章中,我们将探讨如何使用Promise来实现并发请求数的限制。
const pLimit = (max) => {
let count = 0
let queue = []
const add = async (options) => {
// 入队
queue.push(run.bind(undefined, options))
// 等待所有同步代码执行完毕, 也就是所有任务入队, 以确保queue的数量不会判断错误
await Promise.resolve()
//执行到这所有同步以执行完毕, 也就是limit(fn), fn都已入队
if (count < max && queue.length > 0) {
queue.shift()()
}
}
const next = () => {
count--
if (queue.length > 0) {
queue.shift()()
}
}
const run = async ({ fn, args, resolve }) => {
count++
//确保返回一定是promise
const result = (async () => fn(...args))()
//这里执行resolve, 并把函数结果提交出去, let result = await limit()
resolve(result)
try {
await result
} catch {}
next()
}
const generator = (fn, ...args) =>
// 用promise包裹以便 let result = await limit(), 可以用result接受函数返回结果
new Promise((resolve) => {
add({
fn,
args,
resolve
})
})
return generator
}
使用方式
let promiseTimeout = (time) => {
return new Promise((resolve) => setTimeout(resolve, time))
}
let limit = pLimit(4)
Array.from({ length: 10 }).forEach(async (item, index) => {
let b = await limit(async (i) => {
// i是index入参
console.log(i)
await promiseTimeout(1000)
return '返回值b' + i
}, index)
console.log(b) // '返回值b' + i
})
配合axios
Array.from({ length: 10 }).forEach((item, index) => {
limit(async () => {
let res = await axios.get('/config.json', {
params: {
index
}
})
console.log(res)
await promiseTimeout(2000)
})
})
方式二.可通过封装axios插件完成
import axios from 'axios'
var instance = axios.create({ baseURL: '/', timeout: 20000 })
const ConcurrencyManager = (axios, MAX_CONCURRENT = 10) => {
let instance = {
queue: [],
running: [],
shiftInitial: () => {
// 用了异步任务,确保发送请求时都已入栈
setTimeout(() => {
if (instance.running.length < MAX_CONCURRENT) {
instance.shift()
}
}, 0)
},
push: (reqHandler) => {
instance.queue.push(reqHandler)
instance.shiftInitial()
},
shift: () => {
if (instance.queue.length) {
const queued = instance.queue.shift()
// 执行resolver, 才会发送请求
queued.resolver(queued.request)
// 入栈
instance.running.push(queued)
}
},
// 请求拦截
requestHandler: (req) => {
// 请求拦截中返回了一个pending状态的promise,
return new Promise((resolve) => {
// 收集当前请求参数,!!!注意, 只有resolve调用才会走到发送请求, 请参考 https://juejin.cn/post/7343534050148728872
instance.push({ request: req, resolver: resolve })
})
},
// 响应成功拦截
responseHandler: (res) => {
instance.running.shift()
instance.shift()
return res
},
// 响应错误拦截
responseErrorHandler: (res) => {
return Promise.reject(instance.responseHandler(res))
},
interceptors: {
request: null,
response: null
},
// 删除拦截器
detach: () => {
axios.interceptors.request.eject(instance.interceptors.request)
axios.interceptors.response.eject(instance.interceptors.response)
}
}
// axios.interceptors.request.use 会返回一个id值,可以用来取消拦截器
instance.interceptors.request = axios.interceptors.request.use(instance.requestHandler)
instance.interceptors.response = axios.interceptors.response.use(
instance.responseHandler,
instance.responseErrorHandler
)
return instance
}
ConcurrencyManager(instance, 5)
Array.from({ length: 10 }).forEach((item, index) => {
instance.get('/config.json', {
index
})
})
借用了两个开源库
p-limit 可实现promise串行,并行
axios-concurrency axios官方限制最大并发数插件