需求:多个接口同时调用,控制并发数量,并最终返回请求结果集合
拿到需求之后首先分析,确定函数签名:函数接收两个参数:请求集合以及最大限制数量,返回一个promise对象
function concurrencyApi(apiList, limit) {
return new Promise()
}
首先简化需求,只看后半句,不由得让人想起promise.all,那么不妨先动手写一个promise.all
Promise.myAll = function (list) {
return new Promise((resolve, reject) => {
let count = 0;
const result = []
let fulCount = 0
for (const [index, item] of list.entries()) {
count++
Promise.resolve(item).then(ress => {
result[index] = ress
fulCount++
if (fulCount === count) {
resolve(result)
}
}).catch(err => {
reject(err)
})
}
if (count === 0) {
resolve(result)
}
})
}
promise.all的思路相对就比较简单了,这里试用for of循环是因为接收的参数不一定是个数组,有可能是个迭代器。
整体思路大概就是每一项promise执行后,都去检查一下当前的完成数量与list总数是否一致,如果一致代表为最后一次完成,则把整体返回结果resolve出来
那为了使整个逻辑看起来更清晰,这里使用面向对象风格;
既然需求的后半句已经完成了,那我们来尝试完成一下前半句需求:控制并发数量
首先定义constructor,初始化必要的参数
class ConcurrencyApi{
constructor(apiList, limit) {
this.apiList = apiList
this.limit = limit
}
}
那下一步,控制并发数量,那这里如果相对异步的流程随心所欲,生成器是再好不过的工具了
class ConcurrencyApi{
constructor(apiList, limit) {
this.apiList = apiList
this.limit = limit
this.generator = this.getApiItem()
}
...
*getApiItem() {
for (let index = 0; index < this.apiList.length; index++) {
const element = this.apiList[index];
yield {
api: element,
key: index
}
}
}
}
那我们可以想象,如果限制数量为一条,那么其实就是N个接口依次执行,那首先就想到了递归调用~
那我们就来写一下需要递归的函数
class ConcurrencyApi{
...
runApi() {
const {value, done} = this.generator.next()
if(done) {
return
}
value.api().then(async res => {
this.resList[value.key] = await res.text() // 这里使用的是fetch,需求对res二次解析
this.runApi()
})
}
}
那如果runApi()这个function只执行一遍,就完成了我们的依次调用,执行一遍就相当于一条赛道,那如果我们想再多个赛道进行接力赛跑,那在首次的时候,我们调用limit次runApi(),就可以得到limit条赛道了,也就是需求中的限制最大并发数
class ConcurrencyApi{
...
init() {
Array(this.limit).fill(1).forEach((item, index) => {
this.runApi()
})
}
}
那到这里为止,并发数量已经限制住了,剩下的就是收集返回结果,思路就参考上面的promise.all的实现思路
class ConcurrencyApi{
constructor(apiList, limit) {
this.apiList = apiList
this.limit = limit
this.generator = this.getApiItem()
this.resList = []
this.getAllresolve = null
}
...
all(res, index) {
this.init()
return new Promise(resolve => {
this.getAllresolve = resolve
})
}
}
首先定义一个all的方法,并且执行init()方法,把resolve给记录下来,等待所有结果并返回
class ConcurrencyApi{
...
runApi() {
const {value, done} = this.generator.next()
if(this.resList.length === this.apiList.length) {
let everyHaveValue = true
for(let i = this.apiList.length - 1; i >= 0; i--) {
if(!this.resList[i]){
everyHaveValue = false
}
}
if(everyHaveValue) {
this.getAllresolve(this.resList)
}
}
if(done) {
return
}
value.api().then(async res => {
this.resList[value.key] = await res.text()
this.runApi()
})
}
}
然后每次执行runapi方法时,通过判定this.resList.length === this.apiList.length是否执行完毕,但注意的是,因为结果需要按顺序收集,所以很可能最后一项的返回结果优先出,那因为通过arr[index]去赋值,resList很大概率成为一个稀疏数组,这里就简单的通过判断每一项是否存在来判断是否全部完成(不够严谨,根据实际业务调整),注意的是,arr.some/arr.every等方法会忽略掉稀疏数组的稀疏项,所以在这里使用some或者every是无效的。
整体代码如下
class ConcurrencyApi{
constructor(apiList, limit) {
this.apiList = apiList
this.limit = limit
this.generator = this.#getApiItem()
this.resList = []
this.getAllresolve = null
}
*#getApiItem() {
for (let index = 0; index < this.apiList.length; index++) {
const element = this.apiList[index];
yield {
api: element,
key: index
}
}
}
#init() {
Array(this.limit).fill(1).forEach((item, index) => {
this.#runApi()
})
}
#runApi() {
const {value, done} = this.generator.next()
if(this.resList.length === this.apiList.length) {
let everyHaveValue = true
for(let i = this.apiList.length - 1; i >= 0; i--) {
if(!this.resList[i]){
everyHaveValue = false
}
}
if(everyHaveValue) {
this.getAllresolve(this.resList)
}
}
if(done) {
return
}
value.api().then(async res => {
this.resList[value.key] = await res.text()
return this.#runApi()
})
}
all(res, index) {
this.#init()
return new Promise(resolve => {
this.getAllresolve = resolve
})
}
}
具体使用
const task = new ConcurrencyApi(list, 3)
task.all().then(res => {
})