Posted at

【備忘録】Javascriptで非同期処理を効率良くさばく方法


はじめに

Javascriptで非同期処理を行う際に、どう処理するのが最も効率的なのかを考えてみたので備忘録として残す。

例えば下記のような非同期処理(verySlowAsync)を100回呼び出す必要があった場合、

最も効率良く、かつPCの負荷がかかり過ぎないように処理する方法について考えてみた。


verySlowAsync.js

async function verySlowAsync(index) {

console.log(index);
return new Promise((resolve) => setTimeout(resolve, Math.random() * 1000));
}


1件ずつ(oneByOne)

まず試したのが一番シンプルな方法。

PCへの負荷は小さいが、1件ずつ処理をしているので遅い。


oneByOne.js

async function verySlowAsync(index) {

console.log(index);
return new Promise((resolve) => setTimeout(resolve, Math.random() * 1000));
}

async function execute() {

console.time('execute');

const INIT = 0;
const MAX = 100;

for (let index = INIT; index < MAX; index++) {
await verySlowAsync(index);
}

console.timeEnd('execute');

}

execute();



全件一括(allAtOnce)

2番目によく見るのがこちらの方法。

全件を一括で実行して、Promise.allで完了を待つやり方。

処理スピード的には最も速いが、verySlowAsyncの内容次第ではPCの負担が大きく、

JavaScript heap out of memoryになる可能性が高い。(経験談)


allAtOnce.js

async function verySlowAsync(index) {

console.log(index);
return new Promise((resolve) => setTimeout(resolve, Math.random() * 1000));
}

async function execute() {

console.time('execute');

const INIT = 0;
const MAX = 100;

let promises = [];

for (let index = INIT; index < MAX; index++) {
promises.push(verySlowAsync(index));
}

await Promise.all(promises);

console.timeEnd('execute');

}

execute();



一定数ごと(byChunk)

全件一括で処理するのは負荷がかかるので、一定数ごとにまとめて実行する方法。

下記のコードでは10件実行するごとに、Promise.allで完了待つ。

一見効率が良さそうだが、10件すべてが完了しないと次に進めないのでまだ無駄が残る。


byChunk.js

async function verySlowAsync(index) {

console.log(index);
return new Promise((resolve) => setTimeout(resolve, Math.random() * 1000));
}

async function execute() {

console.time('execute');

const INIT = 0;
const MAX = 100;
const CHUNK = 10; // まとめて実行する数を定義

let promises = [];

for (let index = INIT; index < MAX; index++) {

promises.push(verySlowAsync(index));

if ((index + 1) % CHUNK === 0) {
await Promise.all(promises);
promises = [];
}

}

console.timeEnd('execute');

}

execute();



同時実行(concurrently)

考えた中では最も効率が良く、無駄が少ない方法。

同時実行できる上限を定義し、上限内で1件ずつ処理が完了すると次に進むというやり方。

一定数ごとの実行とは違い、1つ終わるごとに次に進むので無駄がない。

かつ、上限を設定しているのでPCへの負荷をある程度制御できる。


concurrently.js

async function verySlowAsync(index) {

console.log(index);
return new Promise((resolve) => setTimeout(resolve, Math.random() * 1000));
}

async function execute() {

console.time('execute');

const INIT = 0;
const MAX = 100;
const CONCURRENCY = 10; // 同時実行できる数を定義

let cnt = INIT;
let promises = [];

for (let i = 0; i < CONCURRENCY; i++) {

let p = new Promise((resolve) => {

(async function loop(index) {

if (index < MAX) {
await verySlowAsync(index);
loop(cnt++);
return;
}

resolve();

})(cnt++);

});

promises.push(p);

}

await Promise.all(promises);

console.timeEnd('execute');

}

execute();



最後に

Node.jsはライブラリ等も充実しており、様々な処理をするのに向いています。

Promiseやasync/awaitを活かして効率良く処理する方法を考えていきたいです。