请排好队,一个一个的来 --- 基于Promise.all()实现并发控制

3,252 阅读2分钟

前言

前几天看了作者 zz_jesse 的 写给新手前端的各种文件上传攻略,从小图片到大文件断点续传  ,学习了很多有关上传的知识点。但在大文件分片上传一块,作者有提及分片上传需要做并发限制处理,但他的demo并没有做。抱着学习的心态,我又去网上学习了一番。。


Promise.all()

 Promise.all() 方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.all([p1, p2, p3]);

上面代码中, Promise.all() 方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用下面讲到的 Promise.resolve 方法,将参数转为 Promise 实例,再进一步处理。另外, Promise.all() 方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。 p的状态由p1、p2、p3决定,分成两种情况。

  • 只有p1、p2、p3的状态都变成 fulfilled ,p的状态才会变成 fulfilled ,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
  • 只要p1、p2、p3之中有一个被 rejected ,p的状态就变成 rejected ,此时第一个被 reject 的实例的返回值,会传递给p的回调函数。

Code

  const requestsLimit = (list, limit, asyncHandle) => {
    return new Promise(resolve => {
      let _limit = limit;
      let recordList = []; // 记录异步操作
      let index = 0;
      let listCopy = [].concat(list);
      let asyncList = []; // 正在进行的所有并发异步操作

      const asyncFunc = () => {
        while(_limit--) {
          const data = listCopy.shift()
          if (data) asyncList.push(asyncHandle(data, index++)); 
        }
        
        Promise.all(asyncList).then(response => {
          // 监听并记录每一次请求的结果
          recordList = recordList.concat(response.filter(item => item));


          if (listCopy.length !== 0) {
            _limit = limit;
            asyncList = [];
            asyncFunc() // 数组还未迭代完,递归继续进行迭代
          } else {
            // 所有并发异步操作都完成后,本次并发控制迭代完成,返回记录结果
            resolve(recordList)
          }
        })
      }

      asyncFunc()
    })
  }

Demo

  var dataLists = [1,2,3,4,5,6,7,8];
  
  requestsLimit(dataLists, 3(item, index) => {
    return new Promise(resolve => {
      // 执行异步处理
      setTimeout(() => {
        // 筛选异步处理的结果
        console.log(index)
        if (item % 2 === 0resolve({ item, index })
        else resolve()
      }, Math.random() * 5000)  
    });
  }).then(response => {
    console.log('finish', response)
  })

console.log()

改进(2022.08.31)

这一帖子是之前学习Promise封装的函数,实现的思路考虑的不够全面。但是没想到还是有很多人看到了这个贴子,下面贴上改进的代码。

function requestsLimit(
  list = [],
  limit = 3,
  asyncHandle = () => Promise.resolve()
) {
  return new Promise((r, j) => {
    const _request = list.slice()
    const queue = [] // 任务队列
    const result = [] // 结果数组
    let index = 0
    let abort = false // 中止开关
    if (!_request.length || !limit) return r(result)

    function requestHandler() {
      const item = _request.shift()
      if (!item) {
        // 判断队列是否还有任务
        if (queue.length) return
        return r(result)
      }
      const handler = asyncHandle(item, index++)
      // 回调函数返回 非 Promise 则中止
      abort = !(handler instanceof Promise)
      if (abort) {
        if (queue.length) return
        return r(result)
      }
      queue.push(
        handler.then(res => {
            result.push(res)
          })
          .catch(err => {
            result.push(err)
          })
          .finally(() => {
            queue.shift()
            // 判断队列是否还有任务 或 是否中止
            if (_request.length && !abort) {
              requestHandler()
            } else if (!queue.length) {
              abort ? j(result) : r(result)
            }
          })
      )
    }
    for (let i = 0; i < limit; i++) {
      requestHandler()
    }
  })
}

传送门