最近在寫一個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)
由於請求是異步的,咱們也沒法肯定每一個請求花費的時間,因此只能在回調裏處理。如今咱們有了Promise
,async-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]
Promise
有catch
方法捕獲錯誤,最近新增的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
保證按順序執行。
好了,以上就是所有內容,你有更好的寫法嗎?