五.axios如何限制最大并发数

996 阅读2分钟

在JavaScript中,我们经常需要处理并发请求,但有时我们希望限制并发请求数,以避免过多的请求同时发送到服务器。在这篇文章中,我们将探讨如何使用Promise来实现并发请求数的限制。

1710492482805-20240315_164531.gif

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)
  })
})

20240313_110437.gif

方式二.可通过封装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官方限制最大并发数插件