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<Array> } 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實現的分析。

我覺得寫的挺好,有空了我再來自己手撕一遍。~。~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章