字節(jié)飛書面試promise.all實(shí)現(xiàn)示例
前言
金三銀四,身為大四即將成為畢業(yè)生的我迫不及待地將簡歷投進(jìn)了字節(jié)的飛書部門,本想著掂量一下幾斤幾兩,沒想到這一掂就露餡了??,去大廠的夢想就這么跌倒在了Promsie.all上。但年輕人總是要有斗志的,從哪里跌到就從哪里爬起來!下面是復(fù)盤時(shí)間。
何為Promise.all?
Promise.all 是 es6 Promise 對象上的一個(gè)方法,它的功能就是將多個(gè)Promise實(shí)例包裝成一個(gè)promise實(shí)例。以下是 MDN 對 Promise.all 的描述:
Promise.all() 方法接收一個(gè) promise 的 iterable 類型(注:Array,Map,Set都屬于ES6的iterable類型)的輸入,并且只返回一個(gè)Promise實(shí)例, 那個(gè)輸入的所有 promise 的 resolve 回調(diào)的結(jié)果是一個(gè)數(shù)組。這個(gè)Promise的 resolve 回調(diào)執(zhí)行是在所有輸入的 promise 的 resolve 回調(diào)都結(jié)束,或者輸入的 iterable 里沒有 promise 了的時(shí)候。它的 reject 回調(diào)執(zhí)行是,只要任何一個(gè)輸入的 promise 的 reject 回調(diào)執(zhí)行或者輸入不合法的 promise 就會立即拋出錯(cuò)誤,并且reject的是第一個(gè)拋出的錯(cuò)誤信息。
我戴上我的300度近視眼鏡,仔細(xì)地提取出這段描述中的關(guān)鍵字:
- Promise.all 的返回值是一個(gè)新的 Promise 實(shí)例。
- Promise.all 接受一個(gè)可遍歷的數(shù)據(jù)容器,容器中每個(gè)元素都應(yīng)是 Promise 實(shí)例。咱就是說,假設(shè)這個(gè)容器就是數(shù)組。
- 數(shù)組中每個(gè) Promise 實(shí)例都成功時(shí)(由pendding狀態(tài)轉(zhuǎn)化為fulfilled狀態(tài)),Promise.all 才成功。這些 Promise 實(shí)例所有的 resolve 結(jié)果會按照原來的順序集合在一個(gè)數(shù)組中作為 Promise.all 的 resolve 的結(jié)果。
- 數(shù)組中只要有一個(gè) Promise 實(shí)例失敗(由pendding狀態(tài)轉(zhuǎn)化為rejected狀態(tài)),Promise.all 就失敗。Promise.all 的 .catch() 會捕獲到這個(gè) reject。
原生 Promise.all 測試
咱先看看原生的Promise.all的是啥效果。
const p1 = Promise.resolve('p1')
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p2 延時(shí)一秒')
}, 1000)
})
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p3 延時(shí)兩秒')
}, 2000)
})
const p4 = Promise.reject('p4 rejected')
const p5 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('p5 rejected 延時(shí)1.5秒')
}, 1500)
})
// 所有Promise實(shí)例都成功
Promise.all([p1, p2, p3])
.then(res => {
console.log(res)
})
.catch(err => console.log(err)) // 2秒后打印 [ 'p1', 'p2 延時(shí)一秒', 'p3 延時(shí)兩秒' ]
// 一個(gè)Promise實(shí)例失敗
Promise.all([p1, p2, p4])
.then(res => {
console.log(res)
})
.catch(err => console.log(err)) // p4 rejected
// 一個(gè)延時(shí)失敗的Promise
Promise.all([p1, p2, p5])
.then(res => {
console.log(res)
})
.catch(err => console.log(err)) // 1.5秒后打印 p5 rejected
// 兩個(gè)Promise實(shí)例失敗
Promise.all([p1, p4, p5])
.then(res => {
console.log(res)
})
.catch(err => console.log(err)) // p4 rejected
注意
上面 p4 和 p5 在未傳入 Promise.all 時(shí)需要注釋掉,因?yàn)橐粋€(gè)調(diào)用了 reject 的 Promise 實(shí)例如果沒有使用 .catch() 方法去捕獲錯(cuò)誤會報(bào)錯(cuò)。但如果 Promise 實(shí)例定義了自己的 .catch,就不會觸發(fā) Promise.all 的 .catch() 方法。
OK,理論存在,實(shí)踐開始!
手動實(shí)現(xiàn)Promise.all
Promise.all 接受一個(gè)數(shù)組,返回值是一個(gè)新的 Promise 實(shí)例
Promise.MyAll = function (promises) {
return new Promise((resolve, reject) => {
})
}
數(shù)組中所有 Promise 實(shí)例都成功,Promise.all 才成功。不難想到,咱得需要一個(gè)數(shù)組來收集這些 Promise 實(shí)例的 resolve 結(jié)果。但有句俗話說得好:“不怕一萬,就怕萬一”,萬一數(shù)組里面有元素不是 Promise咋辦 —— 那就得用 Promise.resolve() 把它辦了。這里還有一個(gè)問題,Promise 實(shí)例是不能直接調(diào)用 resolve 方法的,咱得在 .then() 中去收集結(jié)果。注意要保持結(jié)果的順序。
Promise.MyAll = function (promises) {
let arr = []
return new Promise((resolve, reject) => {
promises.forEach((item, i) => {
Promise.resolve(item).then(res => {
arr[i] = res
})
})
})
}
將收集到的結(jié)果(數(shù)組arr)作為參數(shù)傳給外層的 resolve 方法。這里咱們肯定是有一個(gè)判斷條件的,如何判斷所有 Promise 實(shí)例都成功了呢?新手容易寫出這句代碼(沒錯(cuò)就是我本人了??):
if (arr.length === promises.length) resolve(arr)
咱仔細(xì)想想 Promise 使用來干嘛的 —— 處理異步任務(wù)。對呀,異步任務(wù)很多都需要花時(shí)間呀,如果這些 Promise 中最后一個(gè)先完成呢?那 arr 數(shù)組不就只有最后一項(xiàng)了,前面的所有項(xiàng)都是 empty。所以這里咱們應(yīng)該創(chuàng)建一個(gè)計(jì)數(shù)器,每有一個(gè) Promise 實(shí)例成功,計(jì)數(shù)器加一:
Promise.MyAll = function (promises) {
let arr = [],
count = 0
return new Promise((resolve, reject) => {
promises.forEach((item, i) => {
Promise.resolve(item).then(res => {
arr[i] = res
count += 1
if (count === promises.length) resolve(arr)
})
})
})
}
最后就是處理失敗的情況了,這里有兩種寫法,第一種是用 .catch() 方法捕獲失?。?/p>
Promise.MyAll = function (promises) {
let arr = [],
count = 0
return new Promise((resolve, reject) => {
promises.forEach((item, i) => {
Promise.resolve(item).then(res => {
arr[i] = res
count += 1
if (count === promises.length) resolve(arr)
}).catch(reject)
})
})
}
第二種寫法就是給 .then() 方法傳入第二個(gè)參數(shù),這個(gè)函數(shù)是處理錯(cuò)誤的回調(diào)函數(shù):
Promise.MyAll = function (promises) {
let arr = [],
count = 0
return new Promise((resolve, reject) => {
promises.forEach((item, i) => {
Promise.resolve(item).then(res => {
arr[i] = res
count += 1
if (count === promises.length) resolve(arr)
}, reject)
})
})
}
測試案例
致此 Promise.all 大功告成,趕緊拿來測試一下(摩拳擦掌):
const p1 = Promise.resolve('p1')
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p2 延時(shí)一秒')
}, 1000)
})
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p3 延時(shí)兩秒')
}, 2000)
})
const p4 = Promise.reject('p4 rejected')
const p5 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('p5 rejected 延時(shí)1.5秒')
}, 1500)
})
// 所有 Promsie 都成功
Promise.MyAll([p1, p2, p3])
.then(res => console.log(res))
.catch(err => console.log(err)) // 2秒后打印 [ 'p1', 'p2 延時(shí)一秒', 'p3 延時(shí)兩秒' ]
// 一個(gè) Promise 失敗
Promise.MyAll([p1, p2, p4])
.then(res => console.log(res))
.catch(err => console.log(err)) // p4 rejected
// 一個(gè)延時(shí)失敗的 Promise
Promise.MyAll([p1, p2, p5])
.then(res => console.log(res))
.catch(err => console.log(err)) // 1.5秒后打印 p5 rejected 延時(shí)1.5秒
// 兩個(gè)失敗的 Promise
Promise.MyAll([p1, p4, p5])
.then(res => console.log(res))
.catch(err => console.log(err)) // p4 rejected
“OhOhOhOh~~~~”,與原生的 Promise.all運(yùn)行結(jié)果不能說很像,只能說一模一樣。老話說的好,趁熱打鐵——正在火候上。我打開某個(gè)學(xué)習(xí)網(wǎng)站(MDN Web Docs (mozilla.org)),了解到 Promise 對象用于同時(shí)處理多個(gè) Promise 的方法還有 Promise.race、Promise.any、Promise.allSettle。從小老師就教會了咱們舉一反三,仔細(xì)看了這三個(gè)方法的描述之后,我還真給反出來了??。
Promise.race
Promise.race 從字面意思理解就是賽跑,以狀態(tài)變化最快的那個(gè) Promise 實(shí)例為準(zhǔn),最快的 Promise 成功 Promise.race 就成功,最快的 Promise 失敗 Promise.race 就失敗。
咱來看看原生 Promise.race 效果
原生 Promise.race 測試
const p1 = Promise.resolve('p1')
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p2 延時(shí)一秒')
}, 1000)
})
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p3 延時(shí)兩秒')
}, 2000)
})
const p4 = Promise.reject('p4 rejected')
const p5 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('p5 rejected 延時(shí)1秒')
}, 1500)
})
// p1無延時(shí),p2延時(shí)1s,p3延時(shí)2s
Promise.race([p1, p2, p3])
.then(res => console.log(res))
.catch(err => console.log(err)) // p1
// p4無延時(shí)reject
Promise.race([p4, p2, p3])
.then(res => console.log(res))
.catch(err => console.log(err)) // p4 rejected
// p5 延時(shí)1.5秒reject,p2延時(shí)1s
Promise.race([p5, p2, p3])
.then(res => console.log(res))
.catch(err => console.log(err)) // 1s后打印: p2 延時(shí)一秒
理論存在,實(shí)踐開始
手寫Promise.race
整體流程與 Promise 差不多,只是對數(shù)組中的 Promise 實(shí)例處理的邏輯不一樣,這里我們需要將最快改變狀態(tài)的 Promise 結(jié)果作為 Promise.race 的結(jié)果,相對來說就比較簡單了,代碼如下:
Promise.MyRace = function (promises) {
return new Promise((resolve, reject) => {
// 這里不需要使用索引,只要能循環(huán)出每一項(xiàng)就行
for (const item of promises) {
Promise.resolve(item).then(resolve, reject)
}
})
}
測試案例
還是剛才幾個(gè)案例,咱就不重復(fù)寫了??
// p1無延時(shí),p2延時(shí)1s,p3延時(shí)2s Promise.MyRace([p1, p2, p3]) .then(res => console.log(res)) .catch(err => console.log(err)) // p1 // p4無延時(shí)reject Promise.MyRace([p4, p2, p3]) .then(res => console.log(res)) .catch(err => console.log(err)) // p4 rejected // p5 延時(shí)1.5秒reject,p2延時(shí)1s Promise.MyRace([p5, p2, p3]) .then(res => console.log(res)) .catch(err => console.log(err)) // 1s后打印: p2 延時(shí)一秒
可以看到,結(jié)果與原生的 Promise.race 是一致的,成功!
Promise.any
Promise.any 與 Promise.all 可以看做是相反的。Promise.any 中只要有一個(gè) Promise 實(shí)例成功就成功,只有當(dāng)所有的 Promise 實(shí)例失敗時(shí) Promise.any 才失敗,此時(shí)Promise.any 會把所有的失敗/錯(cuò)誤集合在一起,返回一個(gè)失敗的 promise 和AggregateError類型的實(shí)例。MDN 上說這個(gè)方法還處于試驗(yàn)階段,如果 node 或者瀏覽器版本過低可能無法使用,各位看官自行測試下。
原生 Promise.any 測試
const p1 = Promise.resolve('p1')
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p2 延時(shí)一秒')
}, 1000)
})
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p3 延時(shí)兩秒')
}, 2000)
})
const p4 = Promise.reject('p4 rejected')
const p5 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('p5 rejected 延時(shí)1.5秒')
}, 1500)
})
// 所有 Promise 都成功
Promise.any([p1, p2, p3])
.then(res => console.log(res))
.catch(err => console.log(err)) // p1
// 兩個(gè) Promise 成功
Promise.any([p1, p2, p4])
.then(res => console.log(res))
.catch(err => console.log(err)) // p1
// 只有一個(gè)延時(shí)成功的 Promise
Promise.any([p2, p4, p5])
.then(res => console.log(res))
.catch(err => console.log(err)) // p2 延時(shí)1秒
// 所有 Promise 都失敗
Promise.any([p4, p5])
.then(res => console.log(res))
.catch(err => console.log(err)) // AggregateError: All promises were rejected
可以看出,如果 Promise.any 中有多個(gè)成功的 Promise 實(shí)例,則以最快成功的那個(gè)結(jié)果作為自身 resolve 的結(jié)果。
OK,理論存在,實(shí)踐開始
手寫Promise.any
依葫蘆畫瓢,咱們先寫出 Promise.any 的整體結(jié)構(gòu):
Promise.MyAny = function (promises) {
return new Promise((resolve, reject) => {
promises.forEach((item, i) => {
})
})
}
這里跟Promise.all 的邏輯是反的,咱們需要收集 reject 的 Promise,也需要一個(gè)數(shù)組和計(jì)數(shù)器,用計(jì)數(shù)器判斷是否所有的 Promise 實(shí)例都失敗。另外在收集失敗的 Promise 結(jié)果時(shí)咱需要打上一個(gè)失敗的標(biāo)記方便分析結(jié)果。
Promise.MyAny = function (promises) {
let arr = [],
count = 0
return new Promise((resolve, reject) => {
promises.forEach((item, i) => {
Promise.resolve(item).then(resolve, err => {
arr[i] = { status: 'rejected', val: err }
count += 1
if (count === promises.length) reject(new Error('沒有promise成功'))
})
})
})
}
這里我沒有使用 MDN 上規(guī)定的 AggregateError 實(shí)例,手寫嘛,隨心所欲一點(diǎn),寫自己看著舒服的??
測試案例
// 所有 Promise 都成功 Promise.MyAny([p1, p2, p3]) .then(res => console.log(res)) .catch(err => console.log(err)) // p1 // 兩個(gè) Promise 成功 Promise.MyAny([p1, p2, p4]) .then(res => console.log(res)) .catch(err => console.log(err)) // p1 // 只有一個(gè)延時(shí)成功的 Promise Promise.MyAny([p2, p4, p5]) .then(res => console.log(res)) .catch(err => console.log(err)) // p2 延時(shí)1秒 // 所有 Promise 都失敗 Promise.MyAny([p4, p5]) .then(res => console.log(res)) .catch(err => console.log(err)) // 沒有promise成功
Promise.allSettled
有時(shí)候,咱代碼人總是會有點(diǎn)特殊的需求:如果咱希望一組 Promise 實(shí)例無論成功與否,都等它們異步操作結(jié)束了在繼續(xù)執(zhí)行下一步操作,這可如何是好?于是就出現(xiàn)了 Promise.allSettled。
原生 Promise.allSettled 測試
const p1 = Promise.resolve('p1')
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p2 延時(shí)一秒')
}, 1000)
})
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p3 延時(shí)兩秒')
}, 2000)
})
const p4 = Promise.reject('p4 rejected')
const p5 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('p5 rejected 延時(shí)1.5秒')
}, 1500)
})
// 所有 Promise 實(shí)例都成功
Promise.allSettled([p1, p2, p3])
.then(res => console.log(res))
.catch(err => console.log(err))
// [
// { status: 'fulfilled', value: 'p1' },
// { status: 'fulfilled', value: 'p2 延時(shí)一秒' },
// { status: 'fulfilled', value: 'p3 延時(shí)兩秒' }
// ]
// 有一個(gè) Promise 失敗
Promise.allSettled([p1, p2, p4])
.then(res => console.log(res))
.catch(err => console.log(err))
// [
// { status: 'fulfilled', value: 'p1' },
// { status: 'fulfilled', value: 'p2 延時(shí)一秒' },
// { status: 'rejected' , value: 'p4 rejected' }
// ]
// 所有 Promise 都失敗
Promise.allSettled([p4, p5])
.then(res => console.log(res))
.catch(err => console.log(err))
// [
// { status: 'rejected', reason: 'p4 rejected' },
// { status: 'rejected', reason: 'p5 rejected 延時(shí)1.5秒' }
// ]
可以看到,與 Promise.any 類似,Promise.allSettled 也給所有收集到的結(jié)果打上了標(biāo)記。而且 Promise.allSettled 是不會變成 rejected 狀態(tài)的,不管一組 Promise 實(shí)例的各自結(jié)果如何,Promise.allSettled 都會轉(zhuǎn)變?yōu)?fulfilled 狀態(tài)。
OK,理論存在,實(shí)踐開始
手寫 Promise.allSettled
咱就是說,得用個(gè)數(shù)組把所有的 Promise 實(shí)例的結(jié)果(無論成功與否)都收集起來,判斷收集完了(所有 Promise 實(shí)例狀態(tài)都改變了),咱就將這個(gè)收集到的結(jié)果 resolve 掉。收集成功 Promise 結(jié)果的邏輯咱們在 Promise.all 中實(shí)現(xiàn)過,收集失敗 Promise 結(jié)果咱們在 Promise.any 中處理過。這波,這波是依葫蘆畫瓢——照樣。
Promise.MyAllSettled = function (promises) {
let arr = [],
count = 0
return new Promise((resolve, reject) => {
promises.forEach((item, i) => {
Promise.resolve(item).then(res => {
arr[i] = { status: 'fulfilled', val: res }
count += 1
if (count === promises.length) resolve(arr)
}, (err) => {
arr[i] = { status: 'rejected', val: err }
count += 1
if (count === promises.length) resolve(arr)
})
})
})
}
這代碼,邏輯上雖說沒問題,但各位優(yōu)秀的程序員們肯定是看不順眼的,怎么會有兩段重復(fù)的代碼捏,不行,咱得封裝一下。
Promise.MyAllSettled = function (promises) {
let arr = [],
count = 0
return new Promise((resolve, reject) => {
const processResult = (res, index, status) => {
arr[index] = { status: status, val: res }
count += 1
if (count === promises.length) resolve(arr)
}
promises.forEach((item, i) => {
Promise.resolve(item).then(res => {
processResult(res, i, 'fulfilled')
}, err => {
processResult(err, i, 'rejected')
})
})
})
}
perfect,俗話說得好:沒病走兩步。老樣子,給代碼跑幾個(gè)案例。
測試案例
// 所有 Promise 實(shí)例都成功
Promise.MyAllSettled([p1, p2, p3])
.then(res => console.log(res))
.catch(err => console.log(err))
// [
// { status: 'fulfilled', value: 'p1' },
// { status: 'fulfilled', value: 'p2 延時(shí)一秒' },
// { status: 'fulfilled', value: 'p3 延時(shí)兩秒' }
// ]
// 有一個(gè) MyAllSettled 失敗
Promise.allSettled([p1, p2, p4])
.then(res => console.log(res))
.catch(err => console.log(err))
// [
// { status: 'fulfilled', value: 'p1' },
// { status: 'fulfilled', value: 'p2 延時(shí)一秒' },
// { status: 'rejected' , value: 'p4 rejected' }
// ]
// 所有 MyAllSettled 都失敗
Promise.allSettled([p4, p5])
.then(res => console.log(res))
.catch(err => console.log(err))
// [
// { status: 'rejected', reason: 'p4 rejected' },
// { status: 'rejected', reason: 'p5 rejected 延時(shí)1.5秒' }
// ]
致此,大功告成,我可以驕傲地對媽媽說:“媽媽,我再也不怕 Promise.all”了
結(jié)語
這次字節(jié)飛書面試對我來說是一個(gè)巨大的機(jī)遇,第一次體驗(yàn)面大廠的感覺,可能有暴躁老哥要說了:“字節(jié)面試題就這?你是水文章騙贊的吧”。害,沒辦法,主要是我太菜了,從代碼不知為何物到現(xiàn)在前端學(xué)習(xí)者,爾來8月右一周矣,水平確實(shí)比較次,面試官比較和善,就沒有為難我,問的問題都比較基礎(chǔ)。但我仍然收獲頗豐,感謝字節(jié)團(tuán)隊(duì),感謝前端這個(gè)包容、進(jìn)步的環(huán)境,我會好好總結(jié)這次面試,盡可能地提升自己,加油!
參考文章
因?yàn)閷?shí)現(xiàn)不了Promise.all,一場面試涼涼了
Promise 對象 - ECMAScript 6入門 (ruanyifeng.com)
更多關(guān)于字節(jié)面試promise.all的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
微信小程序 動態(tài)的設(shè)置圖片的高度和寬度詳解及實(shí)例代碼
這篇文章主要介紹了微信小程序 動態(tài)的設(shè)置圖片的高度和寬度詳解及實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2017-02-02
Electron?網(wǎng)絡(luò)攔截實(shí)戰(zhàn)示例詳解
這篇文章主要為大家介紹了Electron?網(wǎng)絡(luò)攔截實(shí)戰(zhàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03
PHP:微信小程序 微信支付服務(wù)端集成實(shí)例詳解及源碼下載
這篇文章主要介紹了微信小程序 微信支付服務(wù)端集成實(shí)例詳解及源碼下載的相關(guān)資料,需要的朋友可以參考下2017-01-01
微信小程序 省市區(qū)選擇器實(shí)例詳解(附源碼下載)
這篇文章主要介紹了微信小程序 省市區(qū)選擇器實(shí)例詳解的相關(guān)資料,區(qū)域間手勢滑動切換,標(biāo)題欄高亮隨之切換,反之亦然;當(dāng)前選中標(biāo)題紅色高亮;回到前一級點(diǎn)擊某區(qū)域后,清空子代的區(qū)域,需要的朋友可以參考下2017-01-01
JS前端使用canvas動態(tài)繪制函數(shù)曲線示例詳解
這篇文章主要為大家介紹了JS前端使用canvas畫函數(shù)曲線的示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08
Promise靜態(tài)四兄弟實(shí)現(xiàn)示例詳解
這篇文章主要為大家介紹了Promise靜態(tài)四兄弟實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07

