美文网首页
如何使用 Promise 封装一个控制并发量的请求库

如何使用 Promise 封装一个控制并发量的请求库

作者: Pober_Wong | 来源:发表于2018-09-11 17:13 被阅读297次

    机缘巧合,碰到了一个控制并发量为 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 中取缓存任务并执行,更新并发量。

    相关文章

      网友评论

          本文标题:如何使用 Promise 封装一个控制并发量的请求库

          本文链接:https://www.haomeiwen.com/subject/dprugftx.html