最近接到了一次阿里的電話面試,問的問題都挺有意思的,並且看重的不單單是問題可否回答得上來,還得能明白背後的原理以及可否使用其餘的方式實現面試
其中一個題頗有印象,也很常見,題目: 如何串行執行多個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
複製代碼
arr.reduce((s, v) => {
return s.then(() => delay(v))
}, Promise.resolve())
複製代碼
比較簡單和常見的方式函數
(
async function () {
for (const v of arr) {
await delay(v)
}
}
)()
複製代碼
本質上使用了async/await的功能ui
其實仔細想一想方式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))
}
複製代碼
這是面試官提供的思路,也提到了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)
複製代碼
經過查閱了for await of
的規則,其實for await of
和for 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
循環便可
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函數
一個合格的工程師: 能找到或寫出市面上最主流的實現方式
一個出色的工程師: 能明白其中的原理,並能觸類旁通,有本身的思考。
與君共勉!