如何使用帶有reduce的Promise以及如何在串行和並行處理之間進行選擇
本文譯自How to use async functions with Array.reduce in Javascript - Tamás Sallai 。
在第一篇文章中,我們介紹了async / await 如何幫助執行異步命令,但在異步處理集合時卻無濟於事。在本文中,我們將研究reduce
函數,它是功能最豐富的集合函數,因為它可以模擬所有其他函數。
1. Array.reduce
Reduce 迭代地構造一個值並返回它,它不一定是一個集合。這就是名字的來源,因為它減少了收集到的值。
迭代函數獲取先前的結果( memo
在下面的示例中調用)以及當前值e
。
以下函數對元素進行求和,從0開始(第二個參數 reduce
):
const arr = [1, 2, 3];
const syncRes = arr.reduce((memo, e) => {
return memo + e;
}, 0);
console.log(syncRes);
// 6
memo | e | 結果 |
---|---|---|
0(初始) | 1個 | 1個 |
1個 | 2 | 3 |
3 | 3 | (最終結果)6 |
2. 異步 reduce
異步版本幾乎相同,但每次迭代都會返回一個Promise,因此 memo
將是先前結果的Promise。迭代函數需要 await
它才能計算下一個結果:
// utility function for sleeping
const sleep = (n) => new Promise((res) => setTimeout(res, n));
const arr = [1, 2, 3];
const asyncRes = await arr.reduce(async (memo, e) => {
await sleep(10);
return (await memo) + e;
}, 0);
console.log(asyncRes);
// 6
memo | e | 結果 |
---|---|---|
0(初始) | 1 | Promise (1) |
Promise (1) | 2 | Promise (3) |
Promise (3) | 3 | (最終結果)Promise (6) |
使用的結構async (memo, e) => await memo
,reduce
可以處理任何異步功能,並且可以對其進行await
編輯。
3. 定時
當在 reduce
中並發時有一個有趣的屬性。在同步版本中,元素被一對一處理,這並不奇怪,因為它們依賴於先前的結果。但是,當異步 reduce
運行時,所有迭代函數將開始並行運行,await memo
僅在需要時才等待上一個結果。
3.1 await memo last
在上面的示例中,所有 sleep
並行執行 ,因為await memo
使得函數等待上一個函數完成后執行。
const arr = [1, 2, 3];
const startTime = new Date().getTime();
const asyncRes = await arr.reduce(async (memo, e) => {
await sleep(10);
return (await memo) + e;
}, 0);
console.log(`Took ${new Date().getTime() - startTime} ms`);
// Took 11-13 ms
3.2 await memo first
但是當await memo
最先出現時,這些函數按順序運行:
const arr = [1, 2, 3];
const startTime = new Date().getTime();
const asyncRes = await arr.reduce(async (memo, e) => {
await memo;
await sleep(10);
return (await memo) + e;
}, 0);
console.log(`Took ${new Date().getTime() - startTime} ms`);
// Took 36-38 ms
這種行為通常不是問題,這意味着不依賴於先前結果的所有內容都將立即被計算出來,只有依賴部分正在等待先前的值。
3.3 當並行很重要時
但是在某些情況下,提前做一些事情可能是不可行的。
例如,我有一段代碼可以打印不同的PDF,並使用 pdf-lib
庫將它們拼接到一個文件中。
實現 printPDF
並行運行資源密集型功能:
const result = await printingPages.reduce(async (memo, page) => {
const pdf = await PDFDocument.load(await printPDF(page));
const pdfDoc = await memo;
(await pdfDoc.copyPages(pdf, pdf.getPageIndices()))
.forEach((page) => pdfDoc.addPage(page));
return pdfDoc;
}, PDFDocument.create());
我注意到當我有很多頁面要打印時,它將占用過多的內存並減慢整個過程。
一個簡單的更改,使 printPDF
調用等待上一個調用完成:
const result = await printingPages.reduce(async (memo, page) => {
const pdfDoc = await memo;
const pdf = await PDFDocument.load(await printPDF(page));
(await pdfDoc.copyPages(pdf, pdf.getPageIndices()))
.forEach((page) => pdfDoc.addPage(page));
return pdfDoc;
}, PDFDocument.create());
4. 結論
reduce
函數很容易轉換為異步函數,但是要弄清楚並行性可能很棘手。幸運的是,它很少破壞任何東西,但是在一些資源密集型或速率受限的操作中,了解如何調用函數至關重要。