前端控制请求并发数

272 阅读4分钟

请求并发数常用应用场景

@1 批量操作,大批量并发请求会造成接口阻塞造成等待时间过长或者浏览器卡死等一系列问题,前端控制请求并发数的话就能缓解服务器的压力;

@2 还有大文件分段上传也可以通过控制并发数实现; @3 echarts多图表同时渲染等等.

项目中有限制并发数的插件 p-limit

题目

请实现如下的函数,可以批量请求数据, 所有的URL地址在urls参数中, 同时可以通过max参数 控制请求的并发度。 当所有的请求结束后,需要执行callback回调。 发请求的函数可以直接使用fetch。

image.png

测试案例

// 并发数控制且进行错误数重发
var p1 = () => new Promise((resolve, reject) => setTimeout(reject, 1000, 'p1'));
var p2 = () => new Promise((resolve, reject) => setTimeout(resolve, 1000, 'p2'));
var p3 = () => new Promise((resolve, reject) => setTimeout(reject, 1000, 'p3'));
var p4 = () => new Promise((resolve, reject) => setTimeout(resolve, 1000, 'p4'));
var p5 = () => new Promise((resolve, reject) => setTimeout(reject, 1000, 'p5'));
var p6 = () => new Promise((resolve, reject) => setTimeout(resolve, 1000, 'p6'));
var p7 = () => new Promise((resolve, reject) => setTimeout(reject, 1000, 'p7'));
var p8 = () => new Promise((resolve, reject) => setTimeout(resolve, 1000, 'p8'));
var p9 = () => new Promise((resolve, reject) => setTimeout(reject, 1000, 'p9'));
var p10 = () => new Promise((resolve, reject) => setTimeout(resolve, 1000, 'p10'));
var p11 = () => new Promise((resolve, reject) => setTimeout(reject, 1000, 'p11'));
var p12 = () => new Promise((resolve, reject) => setTimeout(resolve, 1000, 'p12'));
var p13 = () => new Promise((resolve, reject) => setTimeout(reject, 1000, 'p13'));
var p14 = () => new Promise((resolve, reject) => setTimeout(resolve, 1000, 'p14'));
var tasks = [p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14];

方案一

/**
   *Creates an instance of TaskQueues.
   * @param {*} tasks 所有异步请求数
   * @param {*} max 最大并发数
   * @param {*} calltime 失败重发次数
   * @param {*} callback 回调函数
   * @memberof TaskQueues
   */
class TaskQueues {
  constructor(tasks, max, calltime, callback) {
    this.tasks = tasks
    this.max = max
    this.calltime = calltime
    this.callback = callback
    this.running = 0 // 正在执行数
    this.results = [] // 请求结果
    this.next()
  }
  next() {
    while (this.running < this.max && this.tasks.length) {
      console.log('running');
      const request = this.tasks.shift(); // 获取单个异步请求
      let num = 0; // 请求重发次数
      const run = async (request) => { // 接收参数
        try {
          const requestRes = await request()
          console.log('success push');
          this.results.push(requestRes) // 请求结果
          this.running--
          this.next()
        } catch (error) {
          console.log('trying');
          num += 1;
          if (num >= this.calltime) {
            console.log('fail push');
            this.results.push(error)
            this.running--
            this.next()
          } else {
            run(request) // 注意这里参数不要忘了
          }
        }
      }
      run.call(this, request)
      this.running++
    }
    if (typeof this.callback === 'function' && this.running === 0) {
      this.callback.call(null,this.results)
    }
  }
}
new  TaskQueues(tasks, 4, 2, (results)=>console.log(results))

image.png

方案二 迭代方法 Promise

测试用例

var p1 = () => new Promise((resolve, reject) => setTimeout(resolve, 1000, 'p1'));
var p2 = () => new Promise((resolve, reject) => setTimeout(resolve, 1000, 'p2'));
var p3 = () => new Promise((resolve, reject) => setTimeout(resolve, 1000, 'p3'));
var p4 = () => new Promise((resolve, reject) => setTimeout(resolve, 1000, 'p4'));
var p5 = () => new Promise((resolve, reject) => setTimeout(resolve, 1000, 'p5'));
var p6 = () => new Promise((resolve, reject) => setTimeout(resolve, 1000, 'p6'));
var p7 = () => new Promise((resolve, reject) => setTimeout(resolve, 1000, 'p7'));
var p8 = () => new Promise((resolve, reject) => setTimeout(resolve, 1000, 'p8'));
var p9 = () => new Promise((resolve, reject) => setTimeout(resolve, 1000, 'p9'));
var p10 = () => new Promise((resolve, reject) => setTimeout(resolve, 1000, 'p10'));
var p11 = () => new Promise((resolve, reject) => setTimeout(resolve, 1000, 'p11'));
var p12 = () => new Promise((resolve, reject) => setTimeout(resolve, 1000, 'p12'));
var p13 = () => new Promise((resolve, reject) => setTimeout(resolve, 1000, 'p13'));
var p14 = () => new Promise((resolve, reject) => setTimeout(resolve, 1000, 'p14'));
var tasks = [p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14];

迭代方法二

/**
 * @param {*} tasks 异步请求数
 * @param {*} max 最大并发数
 * @param {*} callback 回调函数
 */
async function limitPromise(tasks,max,callback) {
  let results = [], // 请求结果
    running = []; // 正在请求队列
  for (let task of tasks) {
    console.log('running');
    running.push( // 当前请求放入队列中
      task().then(taskRes => {
        results.push(taskRes); // 保存请求结果
        running.splice(running.indexOf(task),1) // 成功后删除自己
      })
    )
    if (running.length === max) {
      console.log('waiting');
      await Promise.race(running)
    }
  }
  Promise.all(running).then(() => callback(results))
}
limitPromise(tasks,4,(results)=>console.log(results));

image.png

方案三 迭代

/**
 *
 * @param {*} tasks  异步请求数
 * @param {*} max 最大请求并发数
 * @param {*} callback 回调函数
 */
function limitRequests(tasks,max,callback) {
  let results = [], // 请求结果
    running = [], // 请求队列
    index = 0;
  const handlerNextFn =  () => {
    if (index === tasks.length) {
      return Promise.resolve()
    }
    console.log('running');
    const task = tasks[index++] // 获取请求
    running.push(task().then((taskRes => {
      results.push(taskRes)
      running.splice(running.indexOf(task),1)
    })))
    let res = null;
    if (running.length === max) {
      res = Promise.race(running)
    } else {
      res = Promise.resolve()
    }
    return res.then(()=>handlerNextFn())
  };
  handlerNextFn().then(() => {
    Promise.all(running).then(() => {
      callback(results)
    })
  })
}
limitRequests(tasks,4,(results)=>console.log(results));