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