如何正確的在 Array.map 使用 async

封面

map 中返回Promises,然后等待結(jié)果

本文譯自How to use async functions with Array.map in Javascript - Tamás Sallai 。

在前面的文章中,我們介紹了 async / await如何幫助執(zhí)行異步命令 ,但在異步處理集合時卻無濟于事。在本文中,我們將研究該map函數(shù),該函數(shù)是最常用的函數(shù),它將數(shù)據(jù)從一種形式轉(zhuǎn)換為另一種形式(這里可以理解為 map具有返回值)。

1. Array.map

map是最簡單和最常見的采集功能。它通過迭代函數(shù)運行每個元素,并返回包含結(jié)果的數(shù)組。

向每個元素添加一的同步版本:

const arr = [1, 2, 3];

const syncRes = arr.map((i) => {
    return i + 1;
});

console.log(syncRes);
// 2,3,4

異步版本需要做兩件事。首先,它需要將每個項目映射到具有新值的 Promise,這是async在函數(shù)執(zhí)行之前添加的內(nèi)容。

其次,它需要等待所有Promises,然后將結(jié)果收集到Array中。幸運的是,Promise.all內(nèi)置調(diào)用正是我們執(zhí)行步驟2所需的。

這使得一個異步的一般模式mapPromise.all(arr.map(async (...) => ...))。

異步實現(xiàn)與同步實現(xiàn)相同:

const arr = [1, 2, 3];

const asyncRes = await Promise.all(arr.map(async (i) => {
    await sleep(10);
    return i + 1;
}));

console.log(asyncRes);
// 2,3,4

2. 并發(fā)

上面的實現(xiàn)為數(shù)組的每個元素并行運行迭代函數(shù)。通常這很好,但是在某些情況下,它可能會消耗過多的資源。當異步函數(shù)訪問 API 或消耗過多的RAM以至于無法一次運行太多RAM時,可能會發(fā)生這種情況。

盡管異步map易于編寫,但要增加并發(fā)控件。在接下來的幾個示例中,我們將研究不同的解決方案。

2.1 批量處理

最簡單的方法是對元素進行分組并逐個處理。這使您可以控制一次可以運行的最大并行任務(wù)數(shù)。但是由于一組必須在下一組開始之前完成,因此每組中最慢的元素成為限制因素。

為了進行分組,下面的示例使用Underscore.jsgroupBy實現(xiàn)。許多庫提供了一種實現(xiàn),并且它們大多數(shù)都是可互換的。Lodash是個例外,因為其 groupBy 不傳遞 item的索引。

如果您不熟悉groupBy,它將通過迭代函數(shù)運行每個元素,并返回一個對象,其鍵為結(jié)果,值為產(chǎn)生該值的元素的列表。

為了使群體最多n的元素,一個迭代器 Math.floor(i / n),其中 i 是元素的索引。例如,一組大小為3的元素將映射以下元素:

0 => 0
1 => 0
2 => 0
3 => 1
4 => 1
5 => 1
6 => 2
...

Javascript實現(xiàn):

const arr = [30, 10, 20, 20, 15, 20, 10];

console.log(
    _.groupBy(arr, (_v, i) => Math.floor(i / 3))
);
// {
//  0: [30, 10, 20],
//  1: [20, 15, 20],
//  2: [10]
// }

最后一組可能比其他組小,但是保證所有組都不會超過最大組大小。

要映射一組,通常的Promise.all(group.map(...))構(gòu)造是很好。

要按順序映射組,我們需要一個reduce,它將先前的結(jié)果(memo)與當前組的結(jié)果連接起來:

return Object.values(groups)
    .reduce(async (memo, group) => [
        ...(await memo),
        ...(await Promise.all(group.map(iteratee)))
    ], []);

此實現(xiàn)基于以下事實:await memo等待上一個結(jié)果的完成才進行下一個任務(wù)。

實現(xiàn)批處理的完整實現(xiàn):

const arr = [30, 10, 20, 20, 15, 20, 10];

const mapInGroups = (arr, iteratee, groupSize) => {
    const groups = _.groupBy(arr, (_v, i) => Math.floor(i / groupSize));

    return Object.values(groups)
        .reduce(async (memo, group) => [
            ...(await memo),
            ...(await Promise.all(group.map(iteratee)))
        ], []);
};

const res = await mapInGroups(arr, async (v) => {
    console.log(`S ${v}`);
    await sleep(v);
    console.log(`F ${v}`);
    return v + 1;
}, 3);

// -- first batch --
// S 30
// S 10
// S 20
// F 10
// F 20
// F 30
// -- second batch --
// S 20
// S 15
// S 20
// F 15
// F 20
// F 20
// -- third batch --
// S 10
// F 10

console.log(res);
// 31,11,21,21,16,21,11

2.2 并行處理

并發(fā)控制的另一種類型是并行執(zhí)行大多數(shù)n任務(wù),并在完成一項任務(wù)時啟動一個新任務(wù)。

我無法為此提供一個簡單的實現(xiàn),但是幸運的是,Bluebird提供了一個開箱即用的庫。這很簡單,只需導入庫并使用Promise.map支持該concurrency選項的功能即可。

在下面的示例中,并發(fā)限制為2,這意味著立即啟動2個任務(wù),然后每完成一個任務(wù),就開始一個新任務(wù),直到?jīng)]有剩余:

const arr = [30, 10, 20, 20, 15, 20, 10];

// Bluebird promise
const res = await Promise.map(arr, async (v) => {
    console.log(`S ${v}`)
    await sleep(v);
    console.log(`F ${v}`);
    return v + 1;
}, {concurrency: 2});

// S 30
// S 10
// F 10
// S 10
// F 30
// S 20
// F 10
// S 15
// F 20
// S 20
// F 15
// S 20
// F 20
// F 20

console.log(res);
// 31,11,21,21,16,21,11

2.3 順序處理

有時,并發(fā)太多,因此應(yīng)該一個接一個地處理元素。

一個簡單的實現(xiàn)是使用并發(fā)性為 1 的 BluebirdPromise。但是在這種情況下,它不保證包括一個庫,因為reduce這樣做很簡單:

const arr = [1, 2, 3];

const res = await arr.reduce(async (memo, v) => {
    const results = await memo;
    console.log(`S ${v}`)
    await sleep(10);
    console.log(`F ${v}`);
    return [...results, v + 1];
}, []);

// S 1
// F 1
// S 2
// F 2
// S 3
// F 3

console.log(res);
// 2,3,4

確保在執(zhí)行任何其他操作之前 await memo,因為如果沒有 await,它仍然會并發(fā)運行!

3. 結(jié)論

map功能很容易轉(zhuǎn)換為異步,因為Promise.all內(nèi)置功能繁重。但是控制并發(fā)需要一些計劃。

推薦閱讀

如果對你有所幫助,可以點贊、收藏。

您的關(guān)注是莫大的鼓勵 ?(^_-)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容