Promise 并发个数限制

简述

当我们需要保证代码在多个异步操作都完成后执行,通常我们会使用Promise.all 来实现。以请求多张图片为例:

// 为了演示方便,我们在此用fetchImage函数来模拟异步请求图片,返回成功提示
function fetchImage(url) {
  // 模拟请求的响应时间在0 - 1s之间随机
  const timeCost = Math.random() * 1000
  return new Promise(resolve => setTimeout(resolve, timeCost, 'get: ' + url))
}

// 待请求的图片
const imageUrls = [
  'pic_1.png',
  'pic_2.png',
  'pic_3.png',
  'pic_4.png',
  'pic_5.png',
  'pic_6.png',
]


Promise
  .all(imageUrls.map(url => fetchImage(url)))
  .then(resList => console.log(resList))

输出为

[
  "get: pic_1.png", 
  "get: pic_2.png", 
  "get: pic_3.png", 
  "get: pic_4.png", 
  "get: pic_5.png", 
  "get: pic_6.png"
]

如果我们对并行的请求数量有限制,Promise.all 自身是不具有这个功能的。
那么接下来,我们依旧以上述的fetchImage为例,来实现一个可以对图片请求进行并行限制的函数:

/**
 * @description 带并发限制的图片并发请求
 * @param {Array} imageUrls 待请求的图片url列表
 * @param {Object} limit 最大并发个数限制
 * @return { Promise } resList
 */
function fetchImageWithLimit(imageUrls, limit = 4)

本文提供两个思路解决该问题:

  1. 通过Promise.all来实现
  2. 通过Promise.race来实现

通过Promise.all实现

步骤如下:

  1. 初始化limit个Promise对象,作为Promise.all的参数
  2. 每个Promise对象去imageUrls中取出一个url进行请求,若无则resolve
  3. 每个Promise对象在当前请求成功后重复步骤2
function fetchImageWithLimit(imageUrls, limit) {
  // copy一份,作为剩余url的记录
  let urls =[ ...imageUrls ]

  // 用来记录url - response 的映射
  // 保证输出列表与输入顺序一致
  let rs = new Map()

  // 递归的去取url进行请求
  function run() {
    if(urls.length > 0) {
      // 取一个,便少一个
      const url = urls.shift()
      // console.log(url, ' [start at] ', ( new Date()).getTime() % 10000)
      return fetchImage(url).then(res => {
        // console.log(url, ' [end at] ', ( new Date()).getTime() % 10000)
        rs.set(url, res)
        return run()
      })
    }
  }

  // 当imageUrls.length < limit的时候,我们也没有必要去创建多余的Promise
  const promiseList = Array(Math.min(limit, imageUrls.length))
    // 这里用Array.protetype.fill做了简写,但不能进一步简写成.fill(run())
    .fill(Promise.resolve())
    .map(promise => promise.then(run))
    
  return Promise.all(promiseList).then(() => imageUrls.map(item => rs.get(item)))
}

去掉代码中console.log,可展示出如下结果:

fetchImageWithLimit(imageUrls, 2)
  .then(res => console.log(res))
  .catch(err => console.error(err))

// 过程中输出依次为(次序每次都可能不一样)
pic_1.png  [start at]  746
pic_2.png  [start at]  746
pic_1.png  [end at]  814
pic_3.png  [start at]  814
pic_3.png  [end at]  1329
pic_4.png  [start at]  1330
pic_2.png  [end at]  1460
pic_5.png  [start at]  1460
pic_4.png  [end at]  2172
pic_6.png  [start at]  2172
pic_5.png  [end at]  2401
pic_6.png  [end at]  2968

// 最终的结果输出
[
  "get: pic_1.png", 
  "get: pic_2.png", 
  "get: pic_3.png", 
  "get: pic_4.png", 
  "get: pic_5.png", 
  "get: pic_6.png"
]

通过Promise.race实现

步骤如下:

  1. 初始化limit个图片请求,作为Promise.race的参数
  2. 当其中任一请求结束,即新建一个图片请求(还有的话),并连同之前未结束的limit - 1个图片请求一起作为新一轮的Promise.race的参数
  3. 重复步骤2,直到图片都已被请求

具体可参考 这篇博客中对于asyncPool实现的分析。

我觉得写的挺好,有空了我再来自己手撕一遍。~。~

你可能感兴趣的:(javascript,web)