async 與 await 的使用方式相對簡單。 當你嘗試在循環中使用await時,事情就會變得複雜一些。
在本文中,分享一些在如果循環中使用await值得注意的問題。
準備一個例子對於這篇文章,假設你想從水果籃中獲取水果的數量。
const fruitBasket = {
apple: 27,
grape: 0,
pear: 14
};
你想從fruitBasket獲得每個水果的數量。 要獲取水果的數量,可以使用getNumFruit函數。
const getNumFruit = fruit => {
return fruitBasket[fruit];
};
const numApples = getNumFruit('apple');
console.log(numApples);
現在,假設fruitBasket是從伺服器上獲取,這裡我們使用 setTimeout 來模擬。
const sleep = ms => {
return new Promise(resolve => setTimeout(resolve, ms))
};
const getNumFruie = fruit => {
return sleep(1000).then(v => fruitBasket[fruit]);
};
getNumFruit("apple").then(num => console.log(num));
最後,假設你想使用await和getNumFruit來獲取異步函數中每個水果的數量。
const control = async _ => {
console.log('Start')
const numApples = await getNumFruit('apple');
console.log(numApples);
const numGrapes = await getNumFruit('grape');
console.log(numGrapes);
const numPears = await getNumFruit('pear');
console.log(numPears);
console.log('End')
}
首先定義一個存放水果的數組:
const fruitsToGet = [「apple」, 「grape」, 「pear」];
循環遍歷這個數組:
const forLoop = async _ => {
console.log('Start');
for (let index = 0; index < fruitsToGet.length; index++) {
}
console.log('End')
}
在for循環中,過上使用getNumFruit來獲取每個水果的數量,並將數量列印到控制臺。
由於getNumFruit返回一個promise,我們使用 await 來等待結果的返回並列印它。
const forLoop = async _ => {
console.log('start');
for (let index = 0; index < fruitsToGet.length; index ++) {
const fruit = fruitsToGet[index];
const numFruit = await getNumFruit(fruit);
console.log(numFruit);
}
console.log('End')
}
當使用await時,希望JavaScript暫停執行,直到等待 promise 返回處理結果。這意味著for循環中的await 應該按順序執行。
結果正如你所預料的那樣。
「Start」;
「Apple: 27」;
「Grape: 0」;
「Pear: 14」;
「End」;
這種行為適用於大多數循環(比如while和for-of循環)…
但是它不能處理需要回調的循環,如forEach、map、filter和reduce。在接下來的幾節中,我們將研究await 如何影響forEach、map和filter。
在 forEach 循環中使用 await首先,使用 forEach 對數組進行遍歷。
const forEach = _ => {
console.log('start');
fruitsToGet.forEach(fruit => {
})
console.log('End')
}
接下來,我們將嘗試使用getNumFruit獲取水果數量。 (注意回調函數中的async關鍵字。我們需要這個async關鍵字,因為await在回調函數中)。
const forEachLoop = _ => {
console.log('Start');
fruitsToGet.forEach(async fruit => {
const numFruit = await getNumFruit(fruit);
console.log(numFruit)
});
console.log('End')
}
我期望控制臺列印以下內容:
「Start」;
「27」;
「0」;
「14」;
「End」;
但實際結果是不同的。在forEach循環中等待返回結果之前,JavaScrip先執行了 console.log('End')。
實際控制臺列印如下:
『Start』
『End』
『27』
『0』
『14』
JavaScript 中的 forEach不支持 promise 感知,也不支持 async 和await,所以不能在 forEach 使用 await 。
在 map 中使用 await如果在map中使用await, map 始終返回promise數組,這是因為異步函數總是返回promise。
const mapLoop = async _ => {
console.log('Start')
const numFruits = await fruitsToGet.map(async fruit => {
const numFruit = await getNumFruit(fruit);
return numFruit;
})
console.log(numFruits);
console.log('End')
}
「Start」;
「[Promise, Promise, Promise]」;
「End」;
如果你在 map 中使用 await,map 總是返回promises,你必須等待promises 數組得到處理。 或者通過await Promise.all(arrayOfPromises)來完成此操作。
const mapLoop = async _ => {
console.log('Start');
const promises = fruitsToGet.map(async fruit => {
const numFruit = await getNumFruit(fruit);
return numFruit;
});
const numFruits = await Promise.all(promises);
console.log(numFruits);
console.log('End')
}
運行結果如下:
如果你願意,可以在promise 中處理返回值,解析後的將是返回的值。
const mapLoop = _ => {
const promises = fruitsToGet.map(async fruit => {
const numFruit = await getNumFruit(fruit);
return numFruit + 100
})
}
「Start」;
「[127, 100, 114]」;
「End」;
當你使用filter時,希望篩選具有特定結果的數組。假設過濾數量大於20的數組。
如果你正常使用filter (沒有 await),如下:
const filterLoop = _ => {
console.log('Start')
const moreThan20 = fruitsToGet.filter(async fruit => {
const numFruit = await fruitBasket[fruit]
return numFruit > 20
})
console.log(moreThan20)
console.log('END')
}
運行結果
Start
["apple"]
END
filter 中的await不會以相同的方式工作。 事實上,它根本不起作用。
const filterLoop = async _ => {
console.log('Start')
const moreThan20 = await fruitsToGet.filter(async fruit => {
const numFruit = fruitBasket[fruit]
return numFruit > 20
})
console.log(moreThan20)
console.log('END')
}
Start
["apple", "grape", "pear"]
END
為什麼會發生這種情況?
當在filter 回調中使用await時,回調總是一個promise。由於promise 總是真的,數組中的所有項都通過filter 。在filter 使用 await類以下這段代碼
const filtered = array.filter(true);
在filter使用 await 正確的三個步驟
使用map返回一個promise 數組
使用 await 等待處理結果
使用 filter 對返回的結果進行處理
const filterLoop = async _ => {
console.log('Start');
const promises = await fruitsToGet.map(fruit => getNumFruit(fruit));
const numFruits = await Promise.all(promises);
const moreThan20 = fruitsToGet.filter((fruit, index) => {
const numFruit = numFruits[index];
return numFruit > 20;
})
console.log(moreThan20);
console.log('End')
}
如果想要計算 fruitBastet中的水果總數。 通常,你可以使用reduce循環遍歷數組並將數字相加。
const reduceLoop = _ => {
console.log('Start');
const sum = fruitsToGet.reduce((sum, fruit) => {
const numFruit = fruitBasket[fruit];
return sum + numFruit;
}, 0)
console.log(sum)
console.log('End')
}
運行結果:
當你在 reduce 中使用await時,結果會變得非常混亂。
const reduceLoop = async _ => {
console.log('Start');
const sum = await fruitsToGet.reduce(async (sum, fruit) => {
const numFruit = await fruitBasket[fruit];
return sum + numFruit;
}, 0)
console.log(sum)
console.log('End')
}
[object Promise]14 是什麼 鬼??
剖析這一點很有趣。
在第一次遍歷中,sum為0。numFruit是27(通過getNumFruit(apple)的得到的值),0 + 27 = 27。
在第二次遍歷中,sum是一個promise。 (為什麼?因為異步函數總是返回promises!)numFruit是0.promise 無法正常添加到對象,因此JavaScript將其轉換為[object Promise]字符串。 [object Promise] + 0 是object Promise] 0。
在第三次遍歷中,sum 也是一個promise。 numFruit是14. [object Promise] + 14是[object Promise] 14。
解開謎團!
這意味著,你可以在reduce回調中使用await,但是你必須記住先等待累加器!
const reduceLoop = async _ => {
console.log('Start');
const sum = await fruitsToGet.reduce(async (promisedSum, fruit) => {
const sum = await promisedSum;
const numFruit = await fruitBasket[fruit];
return sum + numFruit;
}, 0)
console.log(sum)
console.log('End')
}
但是從上圖中看到的那樣,await 操作都需要很長時間。 發生這種情況是因為reduceLoop需要等待每次遍歷完成promisedSum。
有一種方法可以加速reduce循環,如果你在等待promisedSum之前先等待getNumFruits(),那麼reduceLoop只需要一秒鐘即可完成:
const reduceLoop = async _ => {
console.log('Start');
const sum = await fruitsToGet.reduce(async (promisedSum, fruit) => {
const numFruit = await fruitBasket[fruit];
const sum = await promisedSum;
return sum + numFruit;
}, 0)
console.log(sum)
console.log('End')
}
這是因為reduce可以在等待循環的下一個迭代之前觸發所有三個getNumFruit promise。然而,這個方法有點令人困惑,因為你必須注意等待的順序。
在reduce中使用wait最簡單(也是最有效)的方法是
使用map返回一個promise 數組
使用 await 等待處理結果
使用 reduce 對返回的結果進行處理
const reduceLoop = async _ => {
console.log('Start');
const promises = fruitsToGet.map(getNumFruit);
const numFruits = await Promise.all(promises);
const sum = numFruits.reduce((sum, fruit) => sum + fruit);
console.log(sum)
console.log('End')
}
這個版本易於閱讀和理解,需要一秒鐘來計算水果總數。
從上面看出來什麼如果你想連續執行await調用,請使用for循環(或任何沒有回調的循環)。
永遠不要和forEach一起使用await,而是使用for循環(或任何沒有回調的循環)。
不要在 filter 和 reduce 中使用 await,如果需要,先用 map 進一步驟處理,然後在使用 filter 和 reduce進行處理。
文章來自 sf 的小智,有興趣可以關注他的公眾號「大遷世界」
原文連結:https://segmentfault.com/a/1190000019357943