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