<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
恰逢 Promise
也有四個很像的靜態三兄弟(Promise.all
、Promise.allSettled
、Promise.race
、Promise.any
),它們接受的引數型別相同,但各自邏輯處理不同,它們具體會有什麼區別那?別急,下面等小包慢慢道來。
在文章的開始,小包先給大家提出幾個問題:
Promise.all
與 Promise.allSettled
有啥區別啊?Promise.race
的執行機制? Promise.any
吶,兩者有啥區別?Promise.all
在目前手寫題中熱度頻度應該是 top5
級別的,所以我們要深刻掌握 Promise.all
方法。下面首先來簡單回顧一下 all
方法。
Promise.all
方法類似於一群兄弟們並肩前行,引數可以類比為一群兄弟,只有當兄弟全部快樂,all
老大才會收穫快樂;只要有一個兄弟不快樂,老大就不會快樂。
Promise.all()
方法用於將多個 Promise
範例,包裝成一個新的 Promise
範例。
const p = Promise.all([p1, p2, p3]);
Promise.all
方法接受一個陣列做引數,p1、p2、p3
都是 Promise
範例。如果不是 Promise
範例,則會先呼叫 Promise.resolve
方法將引數先轉化為 Promise
範例,之後進行下一步處理。
返回值 p 的狀態由 p1、p2、p3 決定,可以分成兩種情況:
p1、p2、p3
的狀態都變成 fulfilled
,p
的狀態才會變成 fulfilled
,此時 p1、p2、p3
的返回值組成一個陣列,傳遞給 p
的回撥函數。p1、p2、p3
之中有一個被 rejected
,p
的狀態就變成 rejected
,此時第一個被 reject
的範例的返回值,會傳遞給 p
的回撥函數。// 模擬非同步的promise const p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve(1); }, 1000); }); // 普通promise const p2 = Promise.resolve(2); // 常數值 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
狀態由引數執行結果決定,全部成功則返回成功,存有一個失敗則失敗Promise
範例,會通過 Promise.resolve
轉化成 Promise
範例《ES6 入門教學》還指出: Promise.all 方法可以不是陣列,但必須具有 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
型別的處理與陣列相同,如果引數不是 Promise
範例,會先呼叫 Promise.all
轉化為 Promise
範例。
Promise.all
會返回一個新 Promise
物件Promise.all = function (promises) { return new Promise((resolve, reject) => {}); };
all
方法引數可以是陣列,同樣也可以是 Iterator
型別,因此應該使用 for of
迴圈進行遍歷。Promise.all = function (promises) { return new Promise((resolve, reject) => { for (let p of promises) { } }); };
Promise
型別,因此引數使用前先通過 Promise.resolve
轉換Promise.all = function (promises) { return new Promise((resolve, reject) => { for (let p of promises) { // 保證所有的引數為 promise 範例,然後執行後續操作 Promise.resolve(p).then((data) => { //... }); } }); };
Iterator
型別我們是無法得知迭代深度,因此我們要維護一個 count
用來記錄 promise
總數,同時維護 fulfilledCount
代表完成的 promise
數,當 count === fulfilledCount
,代表所有傳入的 Promise
執行成功,返回資料。
Promise.all = function (promises) { let count = 0; // promise總數 let fulfilledCount = 0; // 完成的promise數 return new Promise((resolve, reject) => { for (let p of promises) { count++; // promise總數 + 1 Promise.resolve(p).then((data) => { fulfilledCount++; // 完成的promise數量+1 if (count === fulfilledCount) { // 代表最後一個promise完成了 resolve(); } }); } }); };
有可能有的讀者會好奇,為啥 count === fulfilledCount 可以判斷所有的 promise 都完成了吶?
Promise.then
方法是 microTasks
(微任務),當同步任務執行完畢後,Event Loop
才會去執行 microTasks
。count++
位於同步程式碼部分,因此在執行 promise.then
方法之前,已經成功的計算出 promise
的總數。
然後依次執行 promise.then
方法,fulfilledCount
增加,當 count === fulfilledCount
說明所有的 promise
都已經成功完成了。
返回資料的順序應該是 all
方法中比較難處理的部分。
result
儲存所有 promise
成功的資料for of
迴圈中,使用 let
變數定義 i
,其值等於當前的遍歷索引let
定義的變數不會發生變數提升,因此我們直接令 result[i]
為 promise
成功資料,這樣就可以實現按引數輸入順序輸出結果Promise.all = function (promises) { const result = []; // 儲存promise成功資料 let count = 0; let fulfilledCount = 0; return new Promise((resolve, reject) => { for (let p of promises) { // i為遍歷的第幾個promise // 使用let避免形成閉包問題 let i = count; count++; // 保證所有的引數為 promise 範例,然後執行後續操作 Promise.resolve(p).then((data) => { fulfilledCount++; // 將第i個promise成功資料賦值給對應位置 result[i] = data; if (count === fulfilledCount) { // 代表最後一個promise完成了 // 返回result陣列 resolve(result); } }); } }); };
處理一下邊界情況
promise
失敗——直接呼叫 reject
即可promise
數量為 0
——返回空陣列(規範規定)// 多餘程式碼省略 Promise.all = function (promises) { return new Promise((resolve, reject) => { // 3.捕獲程式碼執行中的異常 try{ for (let p of promises) { Promise.resolve(p).then(data => {} .catch(reject); // 1.直接呼叫reject函數返回失敗原因 }) } // 2.傳入promise數量為0 if (count === 0) { resolve(result) } } catch(error) { reject(error) } }) }
我們把上面的程式碼彙總一下,加上詳細的註釋,同時測試一下手寫 Promise.all
是否成功。
Promise.all = function (promises) { const result = []; // 儲存promise成功資料 let count = 0; // promise總數 let fulfilledCount = 0; //完成promise數量 return new Promise((resolve, reject) => { // 捕獲程式碼執行中的異常 try { for (let p of promises) { // i為遍歷的第幾個promise // 使用let避免形成閉包問題 let i = count; count++; // promise總數 + 1 Promise.resolve(p) .then((data) => { fulfilledCount++; // 完成的promise數量+1 // 將第i個promise成功資料賦值給對應位置 result[i] = data; if (count === fulfilledCount) { // 代表最後一個promise完成了 // 返回result陣列 resolve(result); } }) .catch(reject); // 傳入promise數量為0 if (count === 0) { resolve(result); // 返回空陣列 } } } 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']
不是每群兄弟們都會碰到好老大(all
方法),allSettled
方法他並不管兄弟們的死活,他只管兄弟們是否做了,而他的任務就是把所有兄弟的結果返回。
Promise.allSettled()
方法接受一個陣列作為引數,陣列的每個成員都是一個 Promise
物件,並返回一個新的 Promise
物件。只有等到引數陣列的所有 Promise
物件都發生狀態變更(不管是 fulfilled
還是 rejected
),返回的 Promise
物件才會發生狀態變更。
還是以上面的例子為例,我們來看一下與 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));
從輸出結果我們可以發現:
allSettled
方法只會成功,不會失敗promise
成功,物件屬性值 status: fulfilled
,value
記錄成功值status: rejected
,reason
記錄失敗原因。allSettled
方法也可以接受 Iterator
型別引數allSettled
方法與 all
方法最大的區別在於兩點:
allSettled
方法沒有失敗情況allSettled
方法返回有固定格式我們可以圍繞這兩點改造 all
方法。
all
方法我們是通過計算成功數量來判斷是否終結,allSettled
方法不計較成功失敗,因此我們需要計算成功/失敗總數量即可。
在累加完成總數量的過程中,分情況構造 allSettled
所需要的資料格式: 成功時壓入成功格式,失敗時壓入失敗格式。
由於有了 all
方法手寫的基礎,上面就不一步一步囉嗦的實現了。
Promise.allSettled = function (promises) { const result = []; let count = 0; let totalCount = 0; //完成promise數量 return new Promise((resolve, reject) => { try { for (let p of promises) { let i = count; count++; // promise總數 + 1 Promise.resolve(p) .then((res) => { totalCount++; // 成功時返回成功格式資料 result[i] = { status: "fulfilled", value: res, }; // 執行完成 if (count === totalCount) { resolve(result); } }) .catch((error) => { totalCount++; // 失敗時返回失敗格式資料 result[i] = { status: "rejected", reason: error, }; // 執行完成 if (count === totalCount) { resolve(result); } }); if (count === 0) { resolve(result); } } } catch (error) { reject(error); } }); };
race
方法形象化來講就是賽跑機制,只認第一名,不管是成功的第一還是失敗的第一。
Promise.race()
方法同樣是接收多個 Promise
範例,包裝成一個新的 Promise
範例。
const p = Promise.race([p1, p2, p3]);
上面案例中,只要 p1、p2、p3
之中有一個範例率先改變狀態,p
的狀態就跟著改變。那個率先改變的 Promise
範例的返回值,就傳遞給 p
的回撥函數。
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
改變狀態就返回其對應結果。
因此我們只需監聽每個 promise
的 then
與 catch
方法,當發生狀態改變,直接呼叫 resolve
和 reject
方法即可。
Promise.race(promises) { return new Promise((resolve, reject) => { for (let p of promises) { // Promise.resolve將p進行轉化,防止傳入非Promise範例 // race執行機制為那個範例發生狀態改變,則返回其對應結果 // 因此監聽 Promise.resolve(p).then(resolve).catch(reject); } }) }
any 方法形象化來說是天選唯一,只要第一個成功者。如果全部失敗了,就返回失敗情況。
ES2021
引入了 Promise.any()
方法。該方法接受一組 Promise
範例作為引數,包裝成一個新的 Promise
範例返回。
any
方法與 race
方法很像,也存在短路特性,只要有一個範例變成 fulfilled
狀態,就會返回成功的結果;如果全部失敗,則返回失敗情況。
// 成功的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
通過上述輸出結果我們可以發現:
any
方法也可以接受 Iterator
格式引數promise
範例轉變為 fulfilled
時,any
返回成功的 promise
,值為最早成功的 promise
值。promise
全部失敗時,any
返回失敗的 promise
,值固定為 AggregateError: All promises were rejected上面我們分析了 any
方法的機制:
fulfilled
,any
隨之返回成功的 promise
。因此這裡我們就可以類似使用 race
的方法,監測每個 promise
的成功。rejected
,any
返回 AggregateError: All promises were rejected
。這裡我們可以參考 all
方法的全部成功,才返回成功,因此我們需要累計失敗數量,當 rejectCount === count
時,返回失敗值。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靜態四兄弟實現範例詳解的詳細內容,更多關於Promise靜態實現的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45