多個請求併發執行怎麼寫?

最近在寫一個Node.js程序,功能是下載頁面上的資源,首先拿到頁面資源連接列表,如:css

[
  'https://xxx.com/img/logo.jpg',
  'https://xxx.com/img/bg.jpg',
  'https://xxx.com/css/main.css',
  'https://xxx.com/css/animate.css',
  'https://xxx.com/js/jquery.js',
  'https://xxx.com/js/form.js',
  ...
]

要求是資源並行下載全部資源下載結束後通知收集錯誤的下載連接jquery

若是是傳統作法是遍歷數組發送請求,聲明一個變量記錄請求數,無論成功或失敗,結束後都給這個變量+1,而且調用一個函數,這個函數判斷當前變量是否等於數組長度,相等則表示全部請求已經完成。ajax

// pseudo code
var count = 0
var errs = []
var data = [...]
function request(url) {
  ajax({url: url})
    .success(function () {
       count++
       callback()
    })
    .fail(function () {
      count++
      errs.push(...)
      callback()
    })
}

function callback() {
  if (count === data.length) {
    console.log('done!')
  }
}

data.forEach(request)

由於請求是異步的,咱們也沒法肯定每一個請求花費的時間,因此只能在回調裏處理。如今咱們有了Promiseasync-await,支持同步的寫法,那能夠怎麼寫呢?數組


咱們用setTimeout來模擬請求,數據data = [500, 400, 300, 200, 100]既是每一個請求返回的數據也是每一個請求所需的時間。併發

若是是繼發請求(一個請求結束後再請求後一個),那麼應該是按順序打印,理論上全部請求的總時間等於每一個請求所花時間之和,約等於1500ms;若是是併發請求(假設請求數不會太多,不超過限制),順序是按時間從小到大打印,理論上全部請求的總時間等於最長的那個時間,約等於500ms異步

首先先看下怎麼並行請求請求結束肯定async

// 模擬請求
function request(param) {
  return new Promise(resolve => {
    setTimeout(() => {
       console.log(param)
       resolve()
    }, param)
  })
}
const items = [500, 400, 300, 200, 100]

✘ 直接for循環函數

(() => {
  for (let item of items) {
    request(item)
  }
  console.log('end')
})()
// 輸出:end, 100, 200, 300, 400, 500

上面的輸出能夠看出,請求是並行的,可是沒法肯定什麼結束url

✘ for循環,使用async-awaitcode

(async () => {
  for (let item of items) {
    await request(item)
  }
  console.log('end')
})()
// 輸出:500, 400, 300, 200, 100, end

上面的代碼能夠看出,雖然肯定告終束,但請求是繼發的

✔ 使用Promise.all

(() => {
  Promise.all(items.map(request)).then(res => {
    console.log('end')
  })
})()
// 輸出:100, 200, 300, 400, 500, end

上面的代碼能夠看出,請求是併發的,而且在全部請求結束後打印end,知足條件

咱們不能保證全部的請求都是正常的,接下來看看當有請求出錯時怎麼處理,假設200的請求出錯

function request(param) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (param === 200) {
        // console.log(param, ' failed')
        return reject({
          status: 'error',
          data: param
        })
      }
      // console.log(param, ' success')
      resolve({
        status: 'success',
        data: param
      })
    }, param)
  })
}
const items = [500, 400, 300, 200, 100]

Promisecatch方法捕獲錯誤,最近新增的finally方法能在最後執行

(() => {
  Promise.all(items.map(request))
    .then(res => {
      console.log(res)
    })
    .catch (err => {
      console.log(err)
    })
    .finally(res => {
      console.log('end', res)
    })
})()
// 輸出 {status: "error", data: 200}, end, undefined

上面的輸出能夠看出,若是有錯誤,則不會進入then,而是進入catch,而後進入finally,可是finally不接受參數,只告訴你結束了。若是把上面模擬請求的console.log(...)註釋去掉,還會發現finally是在catch結束後就執行了,而200後面的請求還未結束。

接下來咱們改造下模擬請求,在請求出錯後就catch錯誤

function request(param) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (param === 200) {
        // console.log(param, ' failed')
        return reject({
          status: 'error',
          data: param
        })
      }
      // console.log(param, ' success')
      resolve({
        status: 'success',
        data: param
      })
    }, param)
  }).catch(err => err)
}

(() => {
  Promise.all(items.map(request))
    .then(res => {
      console.log(res, 'end')
    })
})()
// 輸出 [{…}, {…}, {…}, {stauts: 'error', data: 200}, {…}], end

這樣就能夠在then中拿到所有的結果了,若是要用for循環的話也是能夠的

(async () => {
  const temp = []
  // 這個for循環的做用和上面的map相似
  for (let item of items) {
    temp.push(request(item))
  }

  const result = []
  for (let t of temp) {
    result.push(await t)
  }
  console.log(result, 'end')
})()
// 輸出與上面一致

第一個for循環保證併發請求,保存了Promise,第二個循環加入await保證按順序執行。

好了,以上就是所有內容,你有更好的寫法嗎?

相關文章
相關標籤/搜索