探究一道價值25k的螞蟻金服非同步序列面試題
前言
朋友去面試螞蟻金服,遇到了一道面試題,乍一看感覺挺簡單的,但是實現起來發現內部值得一提的點還是挺多的。
先看題目:
const delay = (ms) => new Promise((resolve) => setTimeout(resolve,ms)); const subFlow = createFlow([() => delay(1000).then(() => log("c"))]); createFlow([ () => log("a"),() => log("b"),subFlow,[() => delay(1000).then(() => log("d")),() => log("e")],]).run(() => { console.log("done"); }); // 需要按照 a,b,延遲1秒,c,d,e,done 的順序列印
按照上面的測試用例,實現 createFlow:
- flow 是指一系列 effects 組成的邏輯片段。
- flow 支援巢狀。
- effects 的執行只需要支援序列。
分析
先以入參分析,createFlow 接受一個數組作為引數(按照題意裡面的每一項應該叫做 effect),排除掉一些重複的項,我們把引數陣列中的每一項整理歸類一下,總共有如下幾種型別:
普通函式:
() => log("a");
延遲函式(Promise):
() => delay(1000).then(() => log("d"));
另一個 flow:
const subFlow = createFlow([() => delay(1000).then(() => log("c"))]);
用陣列包裹的上述三項。
實現
先把引數淺拷貝一份(編寫庫函式,儘量不要影響使用者傳入的引數是個原則),再簡單的扁平化 flat 一下。(處理情況 4)
function createFlow(effects = []) { let sources = effects.slice().flat(); }
觀察題意,createFlow 並不會讓方法開始執行,需要 .run() 之後才會開始執行,所以先定義好這個函式:
function createFlow(effects = []) { let sources = effects.slice().flat(); function run(callback) { while (sources.length) { const task = sources.shift(); } callback?.(); } }
這裡我選擇用 while 迴圈依次處理陣列中的每個 effect,便於隨時中斷。
對於函式型別的 effect,直接執行它:
function createFlow(effects = []) { let sources = effects.slice().flat(); function run(callback) { while (sources.length) { const task = sources.shift(); if (typeof task === "function") { const res = task(); } } // 在所有任務執行完畢後 執行傳入的回撥函式 callback?.(); } return { run,isFlow: true,}; }
這裡拿到了函式的返回值 res,有一個情況別忘了,就是 effect 返回的是一個 Promise,比如這種情況:
() => delay(1000).then(() => log("d"));
那麼拿到返回值後,這裡直接簡化判斷,看返回值是否有 then 屬性來判斷它是否是一個 Promise(生產環境請選擇更加嚴謹的方法)。
if (res?.then) { res.then(createFlow(sources).run); return; }
這裡我選擇中斷本次的 flow 執行,並且用剩下的 sources 去建立一個新的 flow,並且在上一個 Promise 的 then 方法裡再去非同步的開啟新的 flow 的 run。
這樣,上面延遲 1s 後的 Promise 被 resolve 之後,剩下的 sources 任務陣列會被新的 flow.run() 驅動,繼續執行。
接下來再處理 effect 是另一個 flow 的情況,注意上面編寫的大致函式體,我們已經讓 createFlow 這個函式返回值帶上 isFlow
這個標記,用來判斷它是否是一個 flow。
// 把callback放到下一個flow的callback時機裡執行 const next = () => createFlow(sources).run(callback) if (typeof task === "function") { const res = task(); if (res?.then) { res.then(next); return; } } else if (task?.isFlow) { task.run(next); return; }
看 else if 的部分,直接呼叫傳入的 flow 的 run,把剩下的 sources 建立的新的 flow,並且把這一輪的 callback 放入到新的 flow 的 callback 位置。在所有的任務都結束後再執行。
定義一個 next 方法,用來在遇到非同步任務或者另一個 flow 的時候
這樣,引數中傳入的 flow 執行完畢後,才會繼續執行剩下的任務,並且在最後執行 callback。
完整程式碼
function createFlow(effects = []) { let sources = effects.slice().flat(); function run(callback) { while (sources.length) { const task = sources.shift(); // 把callback放到下一個flow的callback時機裡執行 const next = () => createFlow(sources).run(callback) if (typeof task === "function") { const res = task(); if (res?.then) { res.then(next); return; } } else if (task?.isFlow) { task.run(next); return; } } callback?.(); } return { run,}; } const delay = () => new Promise((resolve) => setTimeout(resolve,1000)); createFlow([ () => console.log("a"),() => console.log("b"),createFlow([() => console.log("c")]),[() => delay().then(() => console.log("d")),() => console.log("e")],]).run();
總結
這道面試題主要的目的是考察對於非同步序列流的控制,巧妙的利用自身的遞迴設計來處理傳入的引數也是一個 flow的情況,在編寫題目的過程中展示你對 Promise 的熟練運用,一定會讓面試官對你刮目相看的~
到此這篇關於探究一道價值25k的螞蟻金服非同步序列面試題的文章就介紹到這了,更多相關非同步序列面試題內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!