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 實例,則會先調(diào)用 Promise.resolve 方法將參數(shù)先轉(zhuǎn)化為 Promise 實例,之后進行下一步處理。
返回值 p 的狀態(tài)由 p1、p2、p3 決定,可以分成兩種情況:
- 只有
p1、p2、p3的狀態(tài)都變成fulfilled,p的狀態(tài)才會變成fulfilled,此時p1、p2、p3的返回值組成一個數(shù)組,傳遞給p的回調(diào)函數(shù)。 - 只要
p1、p2、p3之中有一個被rejected,p的狀態(tài)就變成rejected,此時第一個被reject的實例的返回值,會傳遞給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. 存在多個失敗的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é)果決定,全部成功則返回成功,存有一個失敗則失敗- 參數(shù)為非
Promise實例,會通過Promise.resolve轉(zhuǎn)化成Promise實例 - 成功后返回一個數(shù)組,數(shù)組內(nèi)數(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 實例,會先調(diào)用 Promise.all 轉(zhuǎn)化為 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轉(zhuǎn)換
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 方法之前,已經(jīng)成功的計算出 promise 的總數(shù)。
然后依次執(zhí)行 promise.then 方法,fulfilledCount 增加,當 count === fulfilledCount 說明所有的 promise 都已經(jīng)成功完成了。
返回數(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ù)輸入順序輸出結(jié)果
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失敗——直接調(diào)用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.直接調(diào)用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 方法他并不管兄弟們的死活,他只管兄弟們是否做了,而他的任務就是把所有兄弟的結(jié)果返回。
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));

從輸出結(jié)果我們可以發(fā)現(xiàn):
allSettled方法只會成功,不會失敗- 返回結(jié)果每個成員為對象,對象的格式固定
- 如果
promise成功,對象屬性值status: fulfilled,value記錄成功值 - 如果 promise 失敗,對象屬性值
status: rejected,reason記錄失敗原因。
- 如果
allSettled方法也可以接受Iterator類型參數(shù)
思路分析
allSettled 方法與 all 方法最大的區(qū)別在于兩點:
allSettled方法沒有失敗情況allSettled方法返回有固定格式
我們可以圍繞這兩點改造 all 方法。
all 方法我們是通過計算成功數(shù)量來判斷是否終結(jié),allSettled 方法不計較成功失敗,因此我們需要計算成功/失敗總數(shù)量即可。
在累加完成總數(shù)量的過程中,分情況構(gòu)造 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 的回調(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 方法就沒有那么多彎彎繞繞了,只要某個 promise 改變狀態(tài)就返回其對應結(jié)果。
因此我們只需監(jiān)聽每個 promise 的 then 與 catch 方法,當發(fā)生狀態(tài)改變,直接調(diào)用 resolve 和 reject 方法即可。
源碼實現(xiàn)
Promise.race(promises) {
return new Promise((resolve, reject) => {
for (let p of promises) {
// Promise.resolve將p進行轉(zhuǎn)化,防止傳入非Promise實例
// race執(zhí)行機制為那個實例發(fā)生狀態(tài)改變,則返回其對應結(jié)果
// 因此監(jiān)聽
Promise.resolve(p).then(resolve).catch(reject);
}
})
}
Promise.any
基礎學習
any 方法形象化來說是天選唯一,只要第一個成功者。如果全部失敗了,就返回失敗情況。
ES2021 引入了 Promise.any() 方法。該方法接受一組 Promise 實例作為參數(shù),包裝成一個新的 Promise 實例返回。
any 方法與 race 方法很像,也存在短路特性,只要有一個實例變成 fulfilled 狀態(tài),就會返回成功的結(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)
})
// 存在一個成功的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ù)- 當一個
promise實例轉(zhuǎn)變?yōu)?fulfilled時,any返回成功的promise,值為最早成功的promise值。 - 當
promise全部失敗時,any返回失敗的promise,值固定為 AggregateError: All promises were rejected
思路分析
上面我們分析了 any 方法的機制:
- 某個實例轉(zhuǎn)化為
fulfilled,any隨之返回成功的promise。因此這里我們就可以類似使用race的方法,監(jiān)測每個promise的成功。 - 全部實例轉(zhuǎn)化為
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)示例詳解的詳細內(nèi)容,更多關于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

