1- Promise.all 的用法
逆向的去實現功能,最關鍵的前提是準確了解API,輸入、輸出、和註意事項。
這裏直接引用MDN:
Promise.all(iterable)?方法返回壹個 Promise 實例,此實例在 iterable 參數內所有的 promise 都“完成(resolved)”或參數中不包含 promise 時回調完成(resolve);如果參數中 promise 有壹個失敗(rejected),此實例回調失敗(reject),失敗的原因是第壹個失敗 promise 的結果。
MDN後面也給出了詳細說明:
此方法在集合多個promise的返回結果時很有用。
完成(Fulfillment):
如果傳入的可叠代對象為空,Promise.all會同步地返回壹個已完成(resolved)狀態的promise。
如果所有傳入的promise都變為完成狀態,或者傳入的可叠代對象內沒有promise,Promise.all返回的promise異步地變為完成。
在任何情況下,Promise.all返回的promise的完成狀態的結果都是壹個數組,它包含所有的傳入叠代參數對象的值(也包括非promise值)。
失敗/拒絕(Rejection):
如果傳入的promise中有壹個失敗(rejected),Promise.all異步地將失敗的那個結果給失敗狀態的回調函數,而不管其它promise是否完成。
個人感覺MDN解釋的比較清楚了,還是雲裏霧裏的話,可以反復細品壹下上面的說明。或者結合下面的代碼去理解。
2 - 手動實現Promise.all
面試美團的時候,面試官看我寫不出來,就說“既然妳知道了輸入和輸出是什麽,應該能寫出來了....”。
面試官其實不是在鄙視我“我不行”,而是在試圖引導我的思路,只是當時自己編程思路太差,最後還是沒寫出來。
但是面試官的提示,確實是壹個很好的思考思路。 先不管完整的Promise.all代碼是什麽樣子,甚至包括優化啥的。先想想"Promise.all(iterable)?方法返回壹個 Promise實例",就這麽簡單的壹句話怎麽寫呢?
function myPromiseAll(arr) { ?// 參數是壹個iterable對象,壹般是數組 // 返回壹個Promise實例return new Promise((resolve, reject) => {
resolve("面試官讓我寫壹個Promise.all");
// 或者
// reject("我太笨了,寫不出來");
});
}
let pResult = myPromiseAll([]); ?// 先不要去想數組有沒有元素
pResult.then(value=>{
console.log(value); ?// 輸出: 面試官讓我寫壹個Promise.all
}, err=>{
console.log(err);
})
好了,如過看懂了,那麽最重要的壹步就完成了。是不是很簡單。
接下來,只要根據MDN的說明,壹步步完善內部函數的功能就行了。
我們先從“完成”情況下手:
完成(Fulfillment):
A. 如果傳入的可叠代對象為空,Promise.all會同步地返回壹個已完成(resolved)狀態的promise。
B. 如果所有傳入的promise都變為完成狀態,或者傳入的可叠代對象內沒有promise,Promise.all返回的promise異步地變為完成。
C. 在任何情況下,Promise.all返回的promise的完成狀態的結果都是壹個數組,它包含所有的傳入叠代參數對象的值(也包括非promise值)。
請先看C,在完成情況下,會始終返回壹個數組.
function myPromiseAll(arr) {// 定義壹個數組
let result = [];
return new Promise((resolve, reject) => {
// 現在只考慮 “在完成情況下” ,會返回壹個數組
resolve(result);
?
});
}
let pResult = myPromiseAll([]);
pResult.then(value=>{
console.log(pResult); ?// 輸出 Promise { <state>: "fulfilled", <value>: [] }
console.log(value); // 輸出:[]
})
那麽下面來實現B,B裏有分兩種情況:
元素是Promise實例
元素不是Promise實例
那先考慮元素不是Promise實例,從簡單的開始
function myPromiseAll(arr) {let result = [];
return new Promise((resolve, reject) => {
for(let i = 0; i < arr.length; i++) {
result.push(arr[i]);
}
resolve(result);
?
});
}
let pResult = myPromiseAll([1,2,3]); ?// 元素不是Promise實例
pResult.then(value=>{
console.log(pResult); // 輸出: ?Promise { <state>: "fulfilled", <value>: (3) […] }
console.log(value); // 輸出: Array(3) [ 1, 2, 3 ]
})
最難的來了,元素都是Promise實例呢?
別慌,先寫頂層設計,再想細節(自上向下編程)
function myPromiseAll(arr) {let result = [];
return new Promise((resolve, reject) => {
for(let i = 0; i < arr.length; i++) {
if(/*如果是Promise實例*/) {
} else {
result.push(arr[i]);
}
}
// 先想想,resolve放在這裏,對不對?
resolve(result);
?
});
}
繼續完善
function myPromiseAll(arr) {let result = [];
return new Promise((resolve, reject) => {
// 數組為空,直接resolve了
if(arr.length == 0) {
resolve(result);
}
for(let i = 0; i < arr.length; i++) {
if(arr[i].then) { // 若元素是Promise實例,則會有then函數,這裏只是簡單的作為判斷標準
// 元素是Promise
arr[i].then(value => {
console.log(value);
result.push(value);
// 想壹想什麽時候resolve呢?--- 所有Promise實例都完成了
if(result.length == arr.length) {
? console.log("所有都完成了")
resolve(result);
}
})
} else {
result.push(arr[i]);
// 這段代碼跟上面重復,想想,能不能提取放到外面,會出現什麽情況呢?
if(result.length == arr.length) {
resolve(result);
}
}
}
});
}
let p1 = new Promise((resolve, reject)=> {
setTimeout(resolve, 2000, "P1 resolved");
})
let p2 = new Promise((resolve, reject)=> {
setTimeout(resolve, 3000, "P2 resolved");
})
let p3 = new Promise((resolve, reject)=> {
setTimeout(resolve, 4000, "P3 resolved");
})
let pResult = myPromiseAll([p1,p2,p3]);
pResult.then(value=>{
console.log(pResult);
console.log(value);
})
// 輸出
// P1 resolved
// P2 resolved
// P3 resolved
// 所有都完成了
// Promise { <state>: "fulfilled", <value>: (3) […] }
// Array(3) [ "P1 resolved", "P2 resolved", "P3 resolved" ]
完成情況寫完了,還剩失敗情況:
如果傳入的 promise 中有壹個失敗(rejected),Promise.all 異步地將失敗的那個結果給失敗狀態的回調函數,而不管其它 promise 是否完成。
function myPromiseAll(arr) {let result = [];
return new Promise((resolve, reject) => {
// 如果數組為空,直接返回空數組
if(arr.length == 0) {
resolve(result);
}
for(let i = 0; i < arr.length; i++) {
if(arr[i].then) { // 若元素是Promise實例,則會有then函數,這裏只是簡單的作為判斷標準
// 元素是Promise
arr[i].then(value => {
console.log(value);
result.push(value);
// 想壹想什麽時候resolve呢?
if(result.length == arr.length) {
console.log("所有都成功了")
resolve(result);
}
}, err => {
console.log("很不幸,其中壹個失敗了");
// 註意到沒, 這裏沒有像上面的判斷 result.length == arr.length, 為什麽?
// 只要碰到 resolve 或 reject ,就結束了
reject(err);
})
} else {
result.push(arr[i]);
// 這段代碼跟上面重復,想想,能不能提取放到外面,會出現什麽情況呢?
if(result.length == arr.length) {
resolve(result);
}
}
}
});
}
let p1 = new Promise((resolve, reject)=> {
setTimeout(reject, 2000, "P1 rejected");
})
let p2 = new Promise((resolve, reject)=> {
setTimeout(resolve, 3000, "P2 resolved");
})
let p3 = new Promise((resolve, reject)=> {
setTimeout(resolve, 4000, "P3 resolved");
})
let pResult = myPromiseAll([p1,p2,p3]);
pResult.then(value=>{
console.log(pResult); ?// 是輸出成功
console.log(value);
}, err => {
console.log(pResult); ? // 還是輸出失敗呢?
console.log(err);
})
// 輸出
// 很不幸,其中壹個失敗了
// Promise { <state>: "rejected" }
// P1 rejected
// P2 resolved
// P3 resolved
為什麽最後還是輸出了 P2 和 P3 的結果呢? 這是因為,盡管遇到了P1就reject了,然而 P2 和 P3 仍在執行。註意MDN說的是“不管其他Promise是否完成”,而不是“其他Promise被stop”。
let p2 = new Promise((resolve, reject)=> {setTimeout(resolve, 3000, "P2 resolved");
})
let p3 = new Promise((resolve, reject)=> {
setTimeout(resolve, 4000, "P3 resolved");
})
let pResult = myPromiseAll([p2,55,p3]);
pResult.then(value=>{
console.log(pResult);
console.log(value); // 輸出 [55, 'P2 resolved', 'P3 resolved']
}, err => {
console.log(pResult);
console.log(err);