螞蟻金服異步串行面試題

前言

朋友去面試螞蟻金服,遇到了一道面試題,乍一看感受挺簡單的,可是實現起來發現內部值得一提的點仍是挺多的。面試

先看題目:數組

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,延遲1秒,d,e, done 的順序打印
複製代碼

按照上面的測試用例,實現 createFlowmarkdown

  • flow 是指一系列 effects 組成的邏輯片斷。
  • flow 支持嵌套。
  • effects 的執行只須要支持串行。

分析

先以入參分析,createFlow 接受一個數組做爲參數(按照題意裏面的每一項應該叫作 effect),排除掉一些重複的項,咱們把參數數組中的每一項整理歸類一下,總共有以下幾種類型:異步

  1. 普通函數:
() => log("a");
複製代碼
  1. 延遲函數(Promise):
() => delay(1000).then(() => log("d"));
複製代碼
  1. 另外一個 flow
const subFlow = createFlow([() => delay(1000).then(() => log("c"))]);
複製代碼
  1. 用數組包裹的上述三項。

實現

先把參數淺拷貝一份(編寫庫函數,儘可能不要影響用戶傳入的參數是個原則),再簡單的扁平化 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,便於隨時中斷。ui

對於函數類型的 effect,直接執行它:spa

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(生產環境請選擇更加嚴謹的方法)。code

if (res?.then) {
  res.then(createFlow(sources).run);
  return;
}
複製代碼

這裏我選擇中斷本次的 flow 執行,而且用剩下的 sources 去創建一個新的 flow,而且在上一個 Promise 的 then 方法裏再去異步的開啓新的 flowrun

這樣,上面延遲 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 的部分,直接調用傳入的 flowrun,把剩下的 sources 建立的新的 flow,而且把這一輪的 callback 放入到新的 flowcallback 位置。在全部的任務都結束後再執行。

定義一個 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,
    isFlow: true,
  };
}
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 的熟練運用,必定會讓面試官對你另眼相看的~

祝你們在大環境很差的狀況下,都能拿到本身滿意的 offer,加油。

相關文章
相關標籤/搜索