Skip to content

并发请求控制

js
/**
 * 
 * 限制异步请求并行请求数量
 * tips: 每个宏任务之后,引擎会立即执行微任务队列中的所有任务,然后再执行其他的宏任务
 * 核心思路是利用事件循环,将异步任务包裹在微任务中,并在循环中(循环次数是限制的最多并发数)执行微任务,
 * 循环遇到微任务会将循环中遇到的微任务都放进微任务队列中,微任务中包裹的异步任务会在下次事件循环执行;
 * 微任务队列中有任何一个异步任务执行完成后就立即执行下一个异步任务(递归,后续的异步任务同样是包裹在微任务队列中),
 * 直到所有任务执行完毕,则停止递归,返回请求结果。
 * @param {Array<string>} urls     需要请求的url数组
 * @param {number}        limits   限制请求并发数量
 * @param {Function}      callback 每次请求执行的回调函数
 */
function parallelRequest(urls, limits, callback) {
  const { length } = urls;
  const result = [];
  let i = 0;
  let finishedNum = 0; // 完成的个数

  return new Promise((resolve) => {
    const request = async index => {
      // 将回调函数(需要执行的网络请求或异步任务)放进微任务队列,并立即执行
      const p = Promise.resolve().then(() => callback(index, urls[index]));
      const res = await p;
      // 存储回调任务的结果
      result[index] = res;
      finishedNum++;
      // 所有请求都完成后,则完成promise并返回结果数组
      if (finishedNum === length) {
        resolve(result);
      }
      if (i < length) {
        request(i);
        i++;
      }
    };

    while(i < limits) {
      request(i)
      i++
    }
  });
}

// test case
const mockFetch = (index, item) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log([index, item])
      resolve({ t: item * 1000, log: 'res' })
    }, item * 1000);
  });
};
// 这里的测试用例用不同时间间隔的计时器模拟网络请求
parallelRequest([2, 1, 3, 2.5, 1.2, 5, 3.5, 2.3], 2, (i, num) => {
  return mockFetch(i, num);
}).then(res => console.log(res, 'result-----'));