面試官: 來講一下如何串行執行多個Promise

最近接到了一次阿里的電話面試,問的問題都挺有意思的,並且看重的不單單是問題可否回答得上來,還得能明白背後的原理以及可否使用其餘的方式實現面試

題目

其中一個題頗有印象,也很常見,題目: 如何串行執行多個Promise。shell

這要換在平時,常常看些面經或寫點代碼也能答出來: 使用Array.prototype.reduce、使用async + 循環 + await、 或者使用新出的for await of數組

面試官: 那你還能說出使用其餘的方式來實現嗎? for await of 的規則如何?閉包

我: ... 卒koa

挑戰

好吧,咱們今天的挑戰就是用各類方式來實現這個需求, 爲此,我準備了一段代碼異步

function delay(time) {
  return new Promise((resolve, reject) => {
    console.log(`wait ${time}s`)
    setTimeout(() => {
      console.log('execute');
      resolve()
    }, time * 1000)
  })
}

const arr = [3, 4, 5];
複製代碼

一個封裝的延遲函數,而後一個裝有3,4,5的數組,需求就是在開始執行時依次等待3, 4, 5秒,並在以後打印對應輸出async

wait 3s // 等待3s

execute
wait 4s // 等待4s

execute
wait 5s // 等待5s

execute
複製代碼

方式1. reduce

arr.reduce((s, v) => {
  return s.then(() => delay(v))
}, Promise.resolve())
複製代碼

比較簡單和常見的方式函數

方式2. async + 循環 + await

(
  async function () {
    for (const v of arr) {
      await delay(v)
    }
  }
)()
複製代碼

本質上使用了async/await的功能ui

方式3. 普通循環

其實仔細想一想方式1的本質是使用一箇中間變量(上一次執行結果)來保存鏈式Promise, 那咱們觸類旁通, 換別的循環也能夠實現this

let p = Promise.resolve()
for (const i of arr) {
  p = p.then(() => delay(i))
}
複製代碼

理論上全部循環方式都能實現,只要找到一個保存鏈式Promise的地方,閉包也好,參數也好。

其實使用while循環時遇到一些坑,例如這樣寫。

let i
let p = Promise.resolve()
while (i = arr.shift()) {
  p = p.then(() => delay(i))
}
複製代碼

思路沒啥問題,問題就在於i放在外層時實際上每次都被改動,這和一道經典的面試題同樣

for(var i = 0, i < 5, i++) {
  setTimeout(() => {
    console.log(i)
  }, i * 1000)
}
複製代碼

事實上他們都會輸出5。因此對於while循環,咱們須要在內部也保存一份,最後我改爲這樣,感受有點蠢,不過也沒想到其餘辦法

let i
let p = Promise.resolve()
while (i = arr.shift()) {
  let s = i
  p = p.then(() => delay(s))
}
複製代碼

方式4. 遞歸

這是面試官提供的思路,也提到了koa,其實koa本身也有研究,其中洋蔥模型來自於koa-compose庫。

function dispatch(i, p = Promise.resolve()) {
  if (!arr[i]) return Promise.resolve()
  return p.then(() => dispatch(i + 1, delay(arr[i])))
}
dispatch(0)
複製代碼

方式5. for await of

經過查閱了for await of的規則,其實for await offor of規則相似,只須要實現一個內部[Symbol.asyncIterator]方法便可

function createAsyncIterable(arr) {
  return {
    [Symbol.asyncIterator]() {
      return {
        i: 0,
        next() {
          if (this.i < arr.length) {
            return delay(arr[this.i]).then(() => ({ value: this.i++, done: false }));
          }

          return Promise.resolve({ done: true });
        }
      };
    }
  }
}

(async function () {
  for await (i of createAsyncIterable(arr)) { }
})();
複製代碼

先建立出一個可異步迭代對象,而後丟到for await of循環便可

方式6. generator

function* gen() {
  for (const v of arr) {
    yield delay(v)
  }
}

function run(gen) {
  const g = gen()

  function next(data) {
    const result = g.next(data)
    if (result.done) return result.value
    result.value.then(function(data) {
      next(data)
    })
  }

  next()
}

run(gen)
複製代碼

先建立一個generator函數,而後再封裝一個自執行run函數

後記

一個合格的工程師: 能找到或寫出市面上最主流的實現方式

一個出色的工程師: 能明白其中的原理,並能觸類旁通,有本身的思考。

與君共勉!

相關文章
相關標籤/搜索