机缘巧合,碰到了一个控制并发量为 5 的需求(类似于微信小程序里一次只能同时上传十张图片的限制)。
这里我们不讨论 Promise 对非 Promise 请求的封装,先假定它返回的是一个 Promise,先以 fetch 为例吧。

实现一

第一反应想到的是肯定需要将 5 个之后的请求全部缓存起来,但遇到两个问题:

  1. 在前五个中存在 resolve 或者 reject 状态时,怎么触发缓存中的 Promise 呢?
  2. 被缓存的 Promise,肯定是不能直接返回 Proimse 了,那怎么办呢?API 接口返回不了东西了。

先看实现

const LIMIT = 5
const cacheQueue = []
let count = 0

function request (params) {
return fetch(params)
.then(res => {
finishReq()
return Promise.resolve(res)
})
.catch(err => {
finishReq()
return Promise.reject(err)
})
}

function finishReq () {
count--
let nextReq = cacheQueue.shift()
nextQueue && nextReq()
}

function limitedRequest (params) {
if(count > LIMIT) {
return (new Promise((resolve) => {
cacheQueue.push(function () {
count++
resolve(request(params))
})
})).then(req => req)
} else {
count++
let curReq = request(params)
return curReq
}
}
  1. 我们先将 fetch 做了基础改动,在 resolve 和 reject 两种情况下分别挂上了请求结束的函数,同时还原了 fetch 应有的返回值。
  2. 核心实现函数 limitedRequest:count 为当前的并发量,当当前并发量大于最大并发量时,返回一个特殊的长期处于一个 pending 状态的 Promise,我们可以在这个 Promise 里将该 Promise 的 resolve 触发函数用一个 匿名函数包裹起来,并在函数体内调用 resolve,向下一级传递一个真正的 Promise(这里就是我们封装好的 request)。接下来将该匿名函数存入我们的缓存队列中。然后在我们要返回的 Promise 后记得将该 Promise 通过 then 函数转化为我们需要的真正用来请求的 Promise,即 then(req => req)。由此,我们被缓存的请求返回了一个被 pending 的 Promise,只有在匿名函数被调用的时候才会执行真实的 then 函数。
  3. finishReq: 每次有请求完成会调用该函数,纠正并发量,该函数会从缓存队列中出队取得我们缓存的匿名函数并执行(触发 pending 状态的 Promise)(别忘记及时更新 count 值)

这种做法主要利用了 resolve 可以被外界引用,从而达到一个能延后触发并正常返回 Proimse 的效果。

相对优雅的方式

By +0 Lee

export function request(params) {
return new Promise((resolve, reject) => { // 将该 promise 的状态代理到真实 promise 上。可以保证未激活的 Promise 同样可以拿到结果
const task = createTask(request, resolve, reject, params)
if (count >= LIMIT) {
cacheQueue.push(task)
} else {
task()
}
})
}

function createTask (caller, resolve, reject, params) {
return function () {
caller(params)
.then(res => resolve(res))
.then(err => reject(err))
.finally(() => {
count--
if (cacheQueue.length) {
let task = cacheQueue.shift()
task()
}
})
count++
}
}

这种方式较为优雅

  1. request 即是对外 API,我们还是返回了一个 Promise,不过把该 Promise 的状态代理到了每一个 task 上,这样只有当 task 被执行的时候对应的 Promise 代码才能被压栈执行。我们只需要在并发量没有超过限制时直接执行我们的 task,否则将该 task(是一个能执行 请求代码的函数)缓存起来
  2. createTask 是核心函数,这里可以接收对应的请求函数,如 fetch,然后在返回的函数中(task 执行才会被执行)将传进来的 resolve 和 reject 传递给真实的请求函数,最后在该请求结束的时候 finally 中取缓存任务并执行,更新并发量。
donation