Promise靜態(tài)四兄弟實現(xiàn)示例詳解
前言
恰逢 Promise
也有四個很像的靜態(tài)三兄弟(Promise.all
、Promise.allSettled
、Promise.race
、Promise.any
),它們接受的參數(shù)類型相同,但各自邏輯處理不同,它們具體會有什么區(qū)別那?別急,下面等小包慢慢道來。
在文章的開始,小包先給大家提出幾個問題:
Promise.all
與Promise.allSettled
有啥區(qū)別啊?Promise.race
的運行機制?Promise.any
吶,兩者有啥區(qū)別?- 四兄弟只能接受數(shù)組作為參數(shù)嗎?
- 四兄弟方法我們應該如何優(yōu)雅完美的實現(xiàn)?
Promise.all
Promise.all
在目前手寫題中熱度頻度應該是 top5
級別的,所以我們要深刻掌握 Promise.all
方法。下面首先來簡單回顧一下 all
方法。
基礎學習
Promise.all
方法類似于一群兄弟們并肩前行,參數(shù)可以類比為一群兄弟,只有當兄弟全部快樂,all
老大才會收獲快樂;只要有一個兄弟不快樂,老大就不會快樂。
Promise.all()
方法用于將多個 Promise
實例,包裝成一個新的 Promise
實例。
const p = Promise.all([p1, p2, p3]);
Promise.all
方法接受一個數(shù)組做參數(shù),p1、p2、p3
都是 Promise
實例。如果不是 Promise
實例,則會先調用 Promise.resolve
方法將參數(shù)先轉化為 Promise
實例,之后進行下一步處理。
返回值 p 的狀態(tài)由 p1、p2、p3 決定,可以分成兩種情況:
- 只有
p1、p2、p3
的狀態(tài)都變成fulfilled
,p
的狀態(tài)才會變成fulfilled
,此時p1、p2、p3
的返回值組成一個數(shù)組,傳遞給p
的回調函數(shù)。 - 只要
p1、p2、p3
之中有一個被rejected
,p
的狀態(tài)就變成rejected
,此時第一個被reject
的實例的返回值,會傳遞給p
的回調函數(shù)。
// 模擬異步的promise const p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve(1); }, 1000); }); // 普通promise const p2 = Promise.resolve(2); // 常數(shù)值 const p3 = 3; // 失敗的promise const p4 = Promise.reject("error"); // 異步失敗的promise const p5 = new Promise((resolve, reject) => { setTimeout(() => { reject("TypeError"); }, 1000); }); // 1. promise全部成功 Promise.all([p1, p2, p3]) .then((data) => console.log(data)) // [1, 2, 3] .catch((error) => console.log(error)); // 2. 存在失敗的promise Promise.all([p1, p2, p3, p4]) .then((data) => console.log(data)) .catch((error) => console.log(error)); // error // 3. 存在多個失敗的promise Promise.all([p1, p2, p3, p4, p5]) .then((data) => console.log(data)) .catch((error) => console.log(error)); // error
從上面案例的輸出中,我們可以得出下列結論:
p
狀態(tài)由參數(shù)執(zhí)行結果決定,全部成功則返回成功,存有一個失敗則失敗- 參數(shù)為非
Promise
實例,會通過Promise.resolve
轉化成Promise
實例 - 成功后返回一個數(shù)組,數(shù)組內數(shù)據(jù)按照參數(shù)順序排列
- 短路效應: 只會返回第一個失敗信息
Iterator 接口參數(shù)
《ES6 入門教程》還指出: Promise.all 方法可以不是數(shù)組,但必須具有 Iterator 接口,且返回的每個成員都是 Promise 實例
說實話,加粗部分小包是沒能完全理解的,難道 Promise.all
使用 Iterator
類型時,要求迭代項都是 Promise
實例嗎?我們以 String
類型為例,看 Promise.all
是否可以支持迭代項為非 Promise
實例。
// ['x', 'i', 'a', 'o', 'b', 'a', 'o'] Promise.all("xiaobao").then((data) => console.log(data));
可見 Promise
對 Iterator
類型的處理與數(shù)組相同,如果參數(shù)不是 Promise
實例,會先調用 Promise.all
轉化為 Promise
實例。
思路分析
Promise.all
會返回一個新Promise
對象
Promise.all = function (promises) { return new Promise((resolve, reject) => {}); };
- (亮點)
all
方法參數(shù)可以是數(shù)組,同樣也可以是Iterator
類型,因此應該使用for of
循環(huán)進行遍歷。
Promise.all = function (promises) { return new Promise((resolve, reject) => { for (let p of promises) { } }); };
- 某些參數(shù)有可能未必是
Promise
類型,因此參數(shù)使用前先通過Promise.resolve
轉換
Promise.all = function (promises) { return new Promise((resolve, reject) => { for (let p of promises) { // 保證所有的參數(shù)為 promise 實例,然后執(zhí)行后續(xù)操作 Promise.resolve(p).then((data) => { //... }); } }); };
Iterator
類型我們是無法得知迭代深度,因此我們要維護一個 count
用來記錄 promise
總數(shù),同時維護 fulfilledCount
代表完成的 promise
數(shù),當 count === fulfilledCount
,代表所有傳入的 Promise
執(zhí)行成功,返回數(shù)據(jù)。
Promise.all = function (promises) { let count = 0; // promise總數(shù) let fulfilledCount = 0; // 完成的promise數(shù) return new Promise((resolve, reject) => { for (let p of promises) { count++; // promise總數(shù) + 1 Promise.resolve(p).then((data) => { fulfilledCount++; // 完成的promise數(shù)量+1 if (count === fulfilledCount) { // 代表最后一個promise完成了 resolve(); } }); } }); };
有可能有的讀者會好奇,為啥 count === fulfilledCount 可以判斷所有的 promise 都完成了吶?
Promise.then
方法是 microTasks
(微任務),當同步任務執(zhí)行完畢后,Event Loop
才會去執(zhí)行 microTasks
。count++
位于同步代碼部分,因此在執(zhí)行 promise.then
方法之前,已經成功的計算出 promise
的總數(shù)。
然后依次執(zhí)行 promise.then
方法,fulfilledCount
增加,當 count === fulfilledCount
說明所有的 promise
都已經成功完成了。
返回數(shù)據(jù)的順序應該是 all
方法中比較難處理的部分。
- 創(chuàng)建一個數(shù)組
result
存儲所有promise
成功的數(shù)據(jù) - 在
for of
循環(huán)中,使用let
變量定義i
,其值等于當前的遍歷索引 let
定義的變量不會發(fā)生變量提升,因此我們直接令result[i]
為promise
成功數(shù)據(jù),這樣就可以實現(xiàn)按參數(shù)輸入順序輸出結果
Promise.all = function (promises) { const result = []; // 存儲promise成功數(shù)據(jù) let count = 0; let fulfilledCount = 0; return new Promise((resolve, reject) => { for (let p of promises) { // i為遍歷的第幾個promise // 使用let避免形成閉包問題 let i = count; count++; // 保證所有的參數(shù)為 promise 實例,然后執(zhí)行后續(xù)操作 Promise.resolve(p).then((data) => { fulfilledCount++; // 將第i個promise成功數(shù)據(jù)賦值給對應位置 result[i] = data; if (count === fulfilledCount) { // 代表最后一個promise完成了 // 返回result數(shù)組 resolve(result); } }); } }); };
處理一下邊界情況
- 某個
promise
失敗——直接調用reject
即可 - 傳入
promise
數(shù)量為0
——返回空數(shù)組(規(guī)范規(guī)定) - 代碼執(zhí)行過程拋出異常 —— 返回錯誤信息
// 多余代碼省略 Promise.all = function (promises) { return new Promise((resolve, reject) => { // 3.捕獲代碼執(zhí)行中的異常 try{ for (let p of promises) { Promise.resolve(p).then(data => {} .catch(reject); // 1.直接調用reject函數(shù)返回失敗原因 }) } // 2.傳入promise數(shù)量為0 if (count === 0) { resolve(result) } } catch(error) { reject(error) } }) }
源碼實現(xiàn)
我們把上面的代碼匯總一下,加上詳細的注釋,同時測試一下手寫 Promise.all
是否成功。
Promise.all = function (promises) { const result = []; // 存儲promise成功數(shù)據(jù) let count = 0; // promise總數(shù) let fulfilledCount = 0; //完成promise數(shù)量 return new Promise((resolve, reject) => { // 捕獲代碼執(zhí)行中的異常 try { for (let p of promises) { // i為遍歷的第幾個promise // 使用let避免形成閉包問題 let i = count; count++; // promise總數(shù) + 1 Promise.resolve(p) .then((data) => { fulfilledCount++; // 完成的promise數(shù)量+1 // 將第i個promise成功數(shù)據(jù)賦值給對應位置 result[i] = data; if (count === fulfilledCount) { // 代表最后一個promise完成了 // 返回result數(shù)組 resolve(result); } }) .catch(reject); // 傳入promise數(shù)量為0 if (count === 0) { resolve(result); // 返回空數(shù)組 } } } catch (error) { reject(error); } }); };
測試代碼(使用案例中的測試代碼,附加 Iterator
類型 Stirng
):
// 1. promise全部成功 Promise.all([p1, p2, p3]) .then((data) => console.log(data)) // [1, 2, 3] .catch((error) => console.log(error)); // 2. 存在失敗的promise Promise.all([p1, p2, p3, p4]) .then((data) => console.log(data)) .catch((error) => console.log(error)); // error // 3. 存在多個失敗的promise Promise.all([p1, p2, p3, p4, p5]) .then((data) => console.log(data)) .catch((error) => console.log(error)); // error // 4. String 類型 Promise.all("zcxiaobao").then((data) => console.log(data)); // ['z', 'c', 'x', 'i', 'a', 'o', 'b', 'a', 'o']
Promise.allSettled
基礎學習
不是每群兄弟們都會碰到好老大(all
方法),allSettled
方法他并不管兄弟們的死活,他只管兄弟們是否做了,而他的任務就是把所有兄弟的結果返回。
Promise.allSettled()
方法接受一個數(shù)組作為參數(shù),數(shù)組的每個成員都是一個 Promise
對象,并返回一個新的 Promise
對象。只有等到參數(shù)數(shù)組的所有 Promise
對象都發(fā)生狀態(tài)變更(不管是 fulfilled
還是 rejected
),返回的 Promise
對象才會發(fā)生狀態(tài)變更。
還是以上面的例子為例,我們來看一下與 Promise.all
方法有啥不同。
// 1. promise 全部成功 Promise.allSettled([p1, p2, p3]) .then((data) => console.log(data)) // [1, 2, 3] .catch((error) => console.log(error)); // 2. 存在失敗的 promise Promise.allSettled([p1, p2, p3, p4]) .then((data) => console.log(data)) .catch((error) => console.log(error)); // error // 3. 存在多個失敗的 promise Promise.allSettled([p1, p2, p3, p4, p5]) .then((data) => console.log(data)) .catch((error) => console.log(error)); // error // 4. 傳入 String 類型 Promise.allSettled("zc").then((data) => console.log(data));
從輸出結果我們可以發(fā)現(xiàn):
allSettled
方法只會成功,不會失敗- 返回結果每個成員為對象,對象的格式固定
- 如果
promise
成功,對象屬性值status: fulfilled
,value
記錄成功值 - 如果 promise 失敗,對象屬性值
status: rejected
,reason
記錄失敗原因。
- 如果
allSettled
方法也可以接受Iterator
類型參數(shù)
思路分析
allSettled
方法與 all
方法最大的區(qū)別在于兩點:
allSettled
方法沒有失敗情況allSettled
方法返回有固定格式
我們可以圍繞這兩點改造 all
方法。
all
方法我們是通過計算成功數(shù)量來判斷是否終結,allSettled
方法不計較成功失敗,因此我們需要計算成功/失敗總數(shù)量即可。
在累加完成總數(shù)量的過程中,分情況構造 allSettled
所需要的數(shù)據(jù)格式: 成功時壓入成功格式,失敗時壓入失敗格式。
源碼實現(xiàn)
由于有了 all
方法手寫的基礎,上面就不一步一步啰嗦的實現(xiàn)了。
Promise.allSettled = function (promises) { const result = []; let count = 0; let totalCount = 0; //完成promise數(shù)量 return new Promise((resolve, reject) => { try { for (let p of promises) { let i = count; count++; // promise總數(shù) + 1 Promise.resolve(p) .then((res) => { totalCount++; // 成功時返回成功格式數(shù)據(jù) result[i] = { status: "fulfilled", value: res, }; // 執(zhí)行完成 if (count === totalCount) { resolve(result); } }) .catch((error) => { totalCount++; // 失敗時返回失敗格式數(shù)據(jù) result[i] = { status: "rejected", reason: error, }; // 執(zhí)行完成 if (count === totalCount) { resolve(result); } }); if (count === 0) { resolve(result); } } } catch (error) { reject(error); } }); };
Promise.race
基礎學習
race
方法形象化來講就是賽跑機制,只認第一名,不管是成功的第一還是失敗的第一。
Promise.race()
方法同樣是接收多個 Promise
實例,包裝成一個新的 Promise
實例。
const p = Promise.race([p1, p2, p3]);
上面案例中,只要 p1、p2、p3
之中有一個實例率先改變狀態(tài),p
的狀態(tài)就跟著改變。那個率先改變的 Promise
實例的返回值,就傳遞給 p
的回調函數(shù)。
const p1 = new Promise((resolve, reject) => { setTimeout(()=> { resolve(1) },1000) }) const p2 = new Promise((resolve, reject) => { setTimeout(()=> { reject(2) },2000) }) const p3 = 3; // 成功在先,失敗在后 Promise.race([p1, p2]).then(res => {console.log(res)}) // 1 // 同步在先,異步在后 Promise.race([p1, p3]).then(res => console.log(res)) // 3 // String Promise.race('zc').then(res => console.log(res)) // z
思路分析
race
方法就沒有那么多彎彎繞繞了,只要某個 promise
改變狀態(tài)就返回其對應結果。
因此我們只需監(jiān)聽每個 promise
的 then
與 catch
方法,當發(fā)生狀態(tài)改變,直接調用 resolve
和 reject
方法即可。
源碼實現(xiàn)
Promise.race(promises) { return new Promise((resolve, reject) => { for (let p of promises) { // Promise.resolve將p進行轉化,防止傳入非Promise實例 // race執(zhí)行機制為那個實例發(fā)生狀態(tài)改變,則返回其對應結果 // 因此監(jiān)聽 Promise.resolve(p).then(resolve).catch(reject); } }) }
Promise.any
基礎學習
any 方法形象化來說是天選唯一,只要第一個成功者。如果全部失敗了,就返回失敗情況。
ES2021
引入了 Promise.any()
方法。該方法接受一組 Promise
實例作為參數(shù),包裝成一個新的 Promise
實例返回。
any
方法與 race
方法很像,也存在短路特性,只要有一個實例變成 fulfilled
狀態(tài),就會返回成功的結果;如果全部失敗,則返回失敗情況。
// 成功的promise const p1 = new Promise((resolve, reject) => { setTimeout(()=> { resolve(1) },1000) }) // 失敗的promise const p2 = new Promise((resolve, reject) => { setTimeout(()=> { reject(2) },2000) }) //失敗的promise const p3 = new Promise((resolve, reject) => { reject(3) }) // 存在一個成功的promise Promise.any([p1,p2]).then(res => console.log(res))// 1 // 全部失敗的promise Promise.any([p2,p3]).then(res => console.log(res)) .catch(error => console.log(error)) // AggregateError: All promises were rejected // String類型 Promise.any('zc').then(res => console.log(res)) // z
通過上述輸出結果我們可以發(fā)現(xiàn):
any
方法也可以接受Iterator
格式參數(shù)- 當一個
promise
實例轉變?yōu)?fulfilled
時,any
返回成功的promise
,值為最早成功的promise
值。 - 當
promise
全部失敗時,any
返回失敗的promise
,值固定為 AggregateError: All promises were rejected
思路分析
上面我們分析了 any
方法的機制:
- 某個實例轉化為
fulfilled
,any
隨之返回成功的promise
。因此這里我們就可以類似使用race
的方法,監(jiān)測每個promise
的成功。 - 全部實例轉化為
rejected
,any
返回AggregateError: All promises were rejected
。這里我們可以參考all
方法的全部成功,才返回成功,因此我們需要累計失敗數(shù)量,當rejectCount === count
時,返回失敗值。
源碼實現(xiàn)
Promise.any = function(promises) { return new Promise((resolve,reject) => { let count = 0; let rejectCount = 0; let errors = []; let i = 0; for (let p of promises) { i = count; count ++; Promise.resolve(p).then(res => { resolve(res) }).catch(error => { errors[i] = error; rejectCount ++; if (rejectCount === count) { return reject(new AggregateError(errors)) } }) } if(count === 0) return reject(new AggregateError('All promises were rejected')) }) }
以上就是Promise靜態(tài)四兄弟實現(xiàn)示例詳解的詳細內容,更多關于Promise靜態(tài)實現(xiàn)的資料請關注腳本之家其它相關文章!
相關文章
umi插件開發(fā)仿dumi項目自動生成導航欄實現(xiàn)詳解
這篇文章主要為大家介紹了umi插件開發(fā)仿dumi項目自動生成導航欄實現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-01-01微信小程序 ES6Promise.all批量上傳文件實現(xiàn)代碼
這篇文章主要介紹了微信小程序 ES6Promise.all批量上傳文件實現(xiàn)代碼的相關資料,需要的朋友可以參考下2017-04-04