實現promise.all


何為Promise.all?

Promise.all 是 es6 Promise 對象上的一個方法,它的功能就是將多個Promise實例包裝成一個promise實例。以下是 MDN 對 Promise.all 的描述:

Promise.all() 方法接收一個 promise 的 iterable 類型(注:Array,Map,Set都屬於ES6的iterable類型)的輸入,並且只返回一個Promise實例, 那個輸入的所有 promise 的 resolve 回調的結果是一個數組。這個Promise的 resolve 回調執行是在所有輸入的 promise 的 resolve 回調都結束,或者輸入的 iterable 里沒有 promise 了的時候。它的 reject 回調執行是,只要任何一個輸入的 promise 的 reject 回調執行或者輸入不合法的 promise 就會立即拋出錯誤,並且reject的是第一個拋出的錯誤信息。

我戴上我的300度近視眼鏡,仔細地提取出這段描述中的關鍵字

  1. Promise.all 的返回值是一個新的 Promise 實例。
  2. Promise.all 接受一個可遍歷的數據容器,容器中每個元素都應是 Promise 實例。咱就是說,假設這個容器就是數組。
  3. 數組中每個 Promise 實例都成功時(由pendding狀態轉化為fulfilled狀態),Promise.all 才成功。這些 Promise 實例所有的 resolve 結果會按照原來的順序集合在一個數組中作為 Promise.allresolve 的結果。
  4. 數組中只要有一個 Promise 實例失敗(由pendding狀態轉化為rejected狀態),Promise.all 就失敗。Promise.all.catch() 會捕獲到這個 reject

原生 Promise.all 測試

咱先看看原生的Promise.all的是啥效果。

const p1 = Promise.resolve('p1') const p2 = new Promise((resolve, reject) => { setTimeout(() => { resolve('p2 延時一秒') }, 1000) }) const p3 = new Promise((resolve, reject) => { setTimeout(() => { resolve('p3 延時兩秒') }, 2000) }) const p4 = Promise.reject('p4 rejected') const p5 = new Promise((resolve, reject) => { setTimeout(() => { reject('p5 rejected 延時1.5秒') }, 1500) }) // 所有Promise實例都成功 Promise.all([p1, p2, p3]) .then(res => { console.log(res) }) .catch(err => console.log(err)) // 2秒后打印 [ 'p1', 'p2 延時一秒', 'p3 延時兩秒' ] // 一個Promise實例失敗 Promise.all([p1, p2, p4]) .then(res => { console.log(res) }) .catch(err => console.log(err)) // p4 rejected // 一個延時失敗的Promise Promise.all([p1, p2, p5]) .then(res => { console.log(res) }) .catch(err => console.log(err)) // 1.5秒后打印 p5 rejected // 兩個Promise實例失敗 Promise.all([p1, p4, p5]) .then(res => { console.log(res) }) .catch(err => console.log(err)) // p4 rejected 復制代碼

注意

上面 p4p5 在未傳入 Promise.all 時需要注釋掉,因為一個調用了 rejectPromise 實例如果沒有使用 .catch() 方法去捕獲錯誤會報錯。但如果 Promise 實例定義了自己的 .catch,就不會觸發 Promise.all.catch() 方法。

OK,理論存在,實踐開始!

手動實現Promise.all

  1. Promise.all 接受一個數組,返回值是一個新的 Promise 實例
Promise.MyAll = function (promises) { return new Promise((resolve, reject) => { }) } 復制代碼
  1. 數組中所有 Promise 實例都成功,Promise.all 才成功。不難想到,咱得需要一個數組來收集這些 Promise 實例的 resolve 結果。但有句俗話說得好:“不怕一萬,就怕萬一”,萬一數組里面有元素不是 Promise咋辦 —— 那就得用 Promise.resolve() 把它辦了。這里還有一個問題,Promise 實例是不能直接調用 resolve 方法的,咱得在 .then() 中去收集結果。注意要保持結果的順序。
Promise.MyAll = function (promises) { let arr = [] return new Promise((resolve, reject) => { promises.forEach((item, i) => { Promise.resolve(item).then(res => { arr[i] = res }) }) }) } 復制代碼
  1. 將收集到的結果(數組arr)作為參數傳給外層的 resolve 方法。這里咱們肯定是有一個判斷條件的,如何判斷所有 Promise 實例都成功了呢?新手容易寫出這句代碼(沒錯就是我本人了😭):
if (arr.length === promises.length) resolve(arr) 復制代碼

咱仔細想想 Promise 使用來干嘛的 —— 處理異步任務。對呀,異步任務很多都需要花時間呀,如果這些 Promise 中最后一個先完成呢?那 arr 數組不就只有最后一項了,前面的所有項都是 empty。所以這里咱們應該創建一個計數器,每有一個 Promise 實例成功,計數器加一:

Promise.MyAll = function (promises) { let arr = [], count = 0 return new Promise((resolve, reject) => { promises.forEach((item, i) => { Promise.resolve(item).then(res => { arr[i] = res count += 1 if (count === promises.length) resolve(arr) }) }) }) } 復制代碼
  1. 最后就是處理失敗的情況了,這里有兩種寫法,第一種是用 .catch() 方法捕獲失敗:
Promise.MyAll = function (promises) { let arr = [], count = 0 return new Promise((resolve, reject) => { promises.forEach((item, i) => { Promise.resolve(item).then(res => { arr[i] = res count += 1 if (count === promises.length) resolve(arr) }).catch(reject) }) }) } 復制代碼

第二種寫法就是給 .then() 方法傳入第二個參數,這個函數是處理錯誤的回調函數:

Promise.MyAll = function (promises) { let arr = [], count = 0 return new Promise((resolve, reject) => { promises.forEach((item, i) => { Promise.resolve(item).then(res => { arr[i] = res count += 1 if (count === promises.length) resolve(arr) }, reject) }) }) } 復制代碼

測試案例

致此 Promise.all 大功告成,趕緊拿來測試一下(摩拳擦掌):

const p1 = Promise.resolve('p1') const p2 = new Promise((resolve, reject) => { setTimeout(() => { resolve('p2 延時一秒') }, 1000) }) const p3 = new Promise((resolve, reject) => { setTimeout(() => { resolve('p3 延時兩秒') }, 2000) }) const p4 = Promise.reject('p4 rejected') const p5 = new Promise((resolve, reject) => { setTimeout(() => { reject('p5 rejected 延時1.5秒') }, 1500) }) // 所有 Promsie 都成功 Promise.MyAll([p1, p2, p3]) .then(res => console.log(res)) .catch(err => console.log(err)) // 2秒后打印 [ 'p1', 'p2 延時一秒', 'p3 延時兩秒' ] // 一個 Promise 失敗 Promise.MyAll([p1, p2, p4]) .then(res => console.log(res)) .catch(err => console.log(err)) // p4 rejected // 一個延時失敗的 Promise Promise.MyAll([p1, p2, p5]) .then(res => console.log(res)) .catch(err => console.log(err)) // 1.5秒后打印 p5 rejected 延時1.5秒 // 兩個失敗的 Promise Promise.MyAll([p1, p4, p5]) .then(res => console.log(res)) .catch(err => console.log(err)) // p4 rejected 復制代碼

“OhOhOhOh~~~~”,與原生的 Promise.all運行結果不能說很像,只能說一模一樣。老話說的好,趁熱打鐵——正在火候上。我打開某個學習網站(MDN Web Docs (mozilla.org)),了解到 Promise 對象用於同時處理多個 Promise 的方法還有 Promise.racePromise.anyPromise.allSettle。從小老師就教會了咱們舉一反三,仔細看了這三個方法的描述之后,我還真給反出來了😄。

Promise.race

Promise.race 從字面意思理解就是賽跑,以狀態變化最快的那個 Promise 實例為准,最快的 Promise 成功 Promise.race 就成功,最快的 Promise 失敗 Promise.race 就失敗。

咱來看看原生 Promise.race 效果

原生 Promise.race 測試

const p1 = Promise.resolve('p1') const p2 = new Promise((resolve, reject) => { setTimeout(() => { resolve('p2 延時一秒') }, 1000) }) const p3 = new Promise((resolve, reject) => { setTimeout(() => { resolve('p3 延時兩秒') }, 2000) }) const p4 = Promise.reject('p4 rejected') const p5 = new Promise((resolve, reject) => { setTimeout(() => { reject('p5 rejected 延時1秒') }, 1500) }) // p1無延時,p2延時1s,p3延時2s Promise.race([p1, p2, p3]) .then(res => console.log(res)) .catch(err => console.log(err)) // p1 // p4無延時reject Promise.race([p4, p2, p3]) .then(res => console.log(res)) .catch(err => console.log(err)) // p4 rejected // p5 延時1.5秒reject,p2延時1s Promise.race([p5, p2, p3]) .then(res => console.log(res)) .catch(err => console.log(err)) // 1s后打印: p2 延時一秒 復制代碼

理論存在,實踐開始

手寫Promise.race

整體流程與 Promise 差不多,只是對數組中的 Promise 實例處理的邏輯不一樣,這里我們需要將最快改變狀態的 Promise 結果作為 Promise.race 的結果,相對來說就比較簡單了,代碼如下:

Promise.MyRace = function (promises) { return new Promise((resolve, reject) => { // 這里不需要使用索引,只要能循環出每一項就行 for (const item of promises) { Promise.resolve(item).then(resolve, reject) } }) } 復制代碼

測試案例

還是剛才幾個案例,咱就不重復寫了😁

// p1無延時,p2延時1s,p3延時2s Promise.MyRace([p1, p2, p3]) .then(res => console.log(res)) .catch(err => console.log(err)) // p1 // p4無延時reject Promise.MyRace([p4, p2, p3]) .then(res => console.log(res)) .catch(err => console.log(err)) // p4 rejected // p5 延時1.5秒reject,p2延時1s Promise.MyRace([p5, p2, p3]) .then(res => console.log(res)) .catch(err => console.log(err)) // 1s后打印: p2 延時一秒 復制代碼

可以看到,結果與原生的 Promise.race 是一致的,成功!

Promise.any

Promise.anyPromise.all 可以看做是相反的。Promise.any 中只要有一個 Promise 實例成功就成功,只有當所有的 Promise 實例失敗時 Promise.any 才失敗,此時Promise.any 會把所有的失敗/錯誤集合在一起,返回一個失敗的 promise AggregateError類型的實例。MDN 上說這個方法還處於試驗階段,如果 node 或者瀏覽器版本過低可能無法使用,各位看官自行測試下。

原生 Promise.any 測試

const p1 = Promise.resolve('p1') const p2 = new Promise((resolve, reject) => { setTimeout(() => { resolve('p2 延時一秒') }, 1000) }) const p3 = new Promise((resolve, reject) => { setTimeout(() => { resolve('p3 延時兩秒') }, 2000) }) const p4 = Promise.reject('p4 rejected') const p5 = new Promise((resolve, reject) => { setTimeout(() => { reject('p5 rejected 延時1.5秒') }, 1500) }) // 所有 Promise 都成功 Promise.any([p1, p2, p3]) .then(res => console.log(res)) .catch(err => console.log(err)) // p1 // 兩個 Promise 成功 Promise.any([p1, p2, p4]) .then(res => console.log(res)) .catch(err => console.log(err)) // p1 // 只有一個延時成功的 Promise Promise.any([p2, p4, p5]) .then(res => console.log(res)) .catch(err => console.log(err)) // p2 延時1秒 // 所有 Promise 都失敗 Promise.any([p4, p5]) .then(res => console.log(res)) .catch(err => console.log(err)) // AggregateError: All promises were rejected 復制代碼

可以看出,如果 Promise.any 中有多個成功的 Promise 實例,則以最快成功的那個結果作為自身 resolve 的結果。

OK,理論存在,實踐開始

手寫Promise.any

  1. 依葫蘆畫瓢,咱們先寫出 Promise.any 的整體結構:
Promise.MyAny = function (promises) { return new Promise((resolve, reject) => { promises.forEach((item, i) => { }) }) } 復制代碼
  1. 這里跟Promise.all 的邏輯是反的,咱們需要收集 rejectPromise,也需要一個數組和計數器,用計數器判斷是否所有的 Promise 實例都失敗。另外在收集失敗的 Promise 結果時咱需要打上一個失敗的標記方便分析結果。
Promise.MyAny = function (promises) { let arr = [], count = 0 return new Promise((resolve, reject) => { promises.forEach((item, i) => { Promise.resolve(item).then(resolve, err => { arr[i] = { status: 'rejected', val: err } count += 1 if (count === promises.length) reject(new Error('沒有promise成功')) }) }) }) } 復制代碼

這里我沒有使用 MDN 上規定的 AggregateError 實例,手寫嘛,隨心所欲一點,寫自己看着舒服的😄

測試案例

// 所有 Promise 都成功 Promise.MyAny([p1, p2, p3]) .then(res => console.log(res)) .catch(err => console.log(err)) // p1 // 兩個 Promise 成功 Promise.MyAny([p1, p2, p4]) .then(res => console.log(res)) .catch(err => console.log(err)) // p1 // 只有一個延時成功的 Promise Promise.MyAny([p2, p4, p5]) .then(res => console.log(res)) .catch(err => console.log(err)) // p2 延時1秒 // 所有 Promise 都失敗 Promise.MyAny([p4, p5]) .then(res => console.log(res)) .catch(err => console.log(err)) // 沒有promise成功 復制代碼

Promise.allSettled

有時候,咱代碼人總是會有點特殊的需求:如果咱希望一組 Promise 實例無論成功與否,都等它們異步操作結束了在繼續執行下一步操作,這可如何是好?於是就出現了 Promise.allSettled

原生 Promise.allSettled 測試

const p1 = Promise.resolve('p1') const p2 = new Promise((resolve, reject) => { setTimeout(() => { resolve('p2 延時一秒') }, 1000) }) const p3 = new Promise((resolve, reject) => { setTimeout(() => { resolve('p3 延時兩秒') }, 2000) }) const p4 = Promise.reject('p4 rejected') const p5 = new Promise((resolve, reject) => { setTimeout(() => { reject('p5 rejected 延時1.5秒') }, 1500) }) // 所有 Promise 實例都成功 Promise.allSettled([p1, p2, p3]) .then(res => console.log(res)) .catch(err => console.log(err)) // [ // { status: 'fulfilled', value: 'p1' }, // { status: 'fulfilled', value: 'p2 延時一秒' }, // { status: 'fulfilled', value: 'p3 延時兩秒' } // ] // 有一個 Promise 失敗 Promise.allSettled([p1, p2, p4]) .then(res => console.log(res)) .catch(err => console.log(err)) // [ // { status: 'fulfilled', value: 'p1' }, // { status: 'fulfilled', value: 'p2 延時一秒' }, // { status: 'rejected' , value: 'p4 rejected' } // ] // 所有 Promise 都失敗 Promise.allSettled([p4, p5]) .then(res => console.log(res)) .catch(err => console.log(err)) // [ // { status: 'rejected', reason: 'p4 rejected' }, // { status: 'rejected', reason: 'p5 rejected 延時1.5秒' } // ] 復制代碼

可以看到,與 Promise.any 類似,Promise.allSettled 也給所有收集到的結果打上了標記。而且 Promise.allSettled 是不會變成 rejected 狀態的,不管一組 Promise 實例的各自結果如何,Promise.allSettled 都會轉變為 fulfilled 狀態。

OK,理論存在,實踐開始

手寫 Promise.allSettled

咱就是說,得用個數組把所有的 Promise 實例的結果(無論成功與否)都收集起來,判斷收集完了(所有 Promise 實例狀態都改變了),咱就將這個收集到的結果 resolve 掉。收集成功 Promise 結果的邏輯咱們在 Promise.all 中實現過,收集失敗 Promise 結果咱們在 Promise.any 中處理過。這波,這波是依葫蘆畫瓢——照樣。

Promise.MyAllSettled = function (promises) { let arr = [], count = 0 return new Promise((resolve, reject) => { promises.forEach((item, i) => { Promise.resolve(item).then(res => { arr[i] = { status: 'fulfilled', val: res } count += 1 if (count === promises.length) resolve(arr) }, (err) => { arr[i] = { status: 'rejected', val: err } count += 1 if (count === promises.length) resolve(arr) }) }) }) } 復制代碼

這代碼,邏輯上雖說沒問題,但各位優秀的程序員們肯定是看不順眼的,怎么會有兩段重復的代碼捏,不行,咱得封裝一下。

Promise.MyAllSettled = function (promises) { let arr = [], count = 0 return new Promise((resolve, reject) => { const processResult = (res, index, status) => { arr[index] = { status: status, val: res } count += 1 if (count === promises.length) resolve(arr) } promises.forEach((item, i) => { Promise.resolve(item).then(res => { processResult(res, i, 'fulfilled') }, err => { processResult(err, i, 'rejected') }) }) }) } 復制代碼

perfect,俗話說得好:沒病走兩步。老樣子,給代碼跑幾個案例。

測試案例

// 所有 Promise 實例都成功 Promise.MyAllSettled([p1, p2, p3]) .then(res => console.log(res)) .catch(err => console.log(err)) // [ // { status: 'fulfilled', value: 'p1' }, // { status: 'fulfilled', value: 'p2 延時一秒' }, // { status: 'fulfilled', value: 'p3 延時兩秒' } // ] // 有一個 MyAllSettled 失敗 Promise.allSettled([p1, p2, p4]) .then(res => console.log(res)) .catch(err => console.log(err)) // [ // { status: 'fulfilled', value: 'p1' }, // { status: 'fulfilled', value: 'p2 延時一秒' }, // { status: 'rejected' , value: 'p4 rejected' } // ] // 所有 MyAllSettled 都失敗 Promise.allSettled([p4, p5]) .then(res => console.log(res)) .catch(err => console.log(err)) // [ // { status: 'rejected', reason: 'p4 rejected' }, // { status: 'rejected', reason: 'p5 rejected 延時1.5秒' } // ] 復制代碼

參考文章

因為實現不了Promise.all,一場面試涼涼了 - 掘金 (juejin.cn)

Promise 對象 - ECMAScript 6入門 (ruanyifeng.com)

鏈接:https://juejin.cn/post/7069805387490263047


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM