美文网首页
如何使用 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 封装一个控制并发量的请求库

    机缘巧合,碰到了一个控制并发量为 5 的需求(类似于微信小程序里一次只能同时上传十张图片的限制)。这里我们不讨论 ...

  • 微信小程序请求改造

    原生小程序请求 promise 封装wx.request 回顾promise使用 开始封装 首先在utils下创建...

  • promise 封装get,post请求

    在uniapp中,使用promise对get,post请求进行封装 然后需要把promise封装的req函数,挂载...

  • 关于Promise

    Promise.all () 可以使用Promise.all 封装多个请求,这时候返回的数据会封装成数组,在使用[...

  • [React-Native]网络请求:Promise

    异步执行的封装 使用Promise的对象封装异步请求,使用resolve和reject分别包装正常返回和异常返回的...

  • Promise

    Promise对象就是一个异步请求占位符对象 把异步请求封装在Promise对象中,Promise的构造函数传入一...

  • Android 知识点盲区

    1、如何控制某个方法允许并发访问线程的个数? semaphore.acquire() 请求一个信号量,这时候的信号...

  • mpvue系列(二)

    封装mpvue请求文件 一、flyio是什么? flyio.js 是一个基于 promise 的,轻量且强大的Ja...

  • vue for循环中按顺序axios请求

    第一步:首先先对请求的方法进行封装Promise 2.第二步:使用async和await再对Promise进行封装...

  • 微信小程序http请求封装

    微信小程序中request请求都是异步请求的,封装的http请求 使用promise请求将异步请求变成同步化,保存...

网友评论

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

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