沢山のタスクがあって並行処理したいのですが、全てを並行実行させると重くなってしまうので、同時実行数を指定しつつ並行実行させたい時があります。
恥ずかしながらJavaScriptでそれをやる方法を知らず、代案として、処理したいタスクを同時実行数づつに分けて、一まとまりをPromise.all()
で実行していました。
しかしこれだと、一まとまりのPromise.all()
に入れたタスクが全て終了してから、次の一まとまりの処理を行うので、常に一定数の同時実行ではないのでモヤモヤしていたのですが、JavaScritで同時実行数を指定しつつ並行実行させる記事を見つけました。
なるほど~。同時実行数だけPromiseを起動して、そのPromiseの中でタスクを実行すればいいんですね。
いや~、勉強になりました!
ずっとモヤモヤしていたのが解決してスッキリしました!ありがとうございます。
写経
実際に手を動かさないと身につかないので、記事を参考にリライトしてみました。
function task(workerId: number, dataId: number, input: number) { return new Promise((resolve, reject) => { console.log(`TASK START : workerId->${workerId} dataId->${dataId} input->${input}`); setTimeout(() => { console.log(`----- TASK END : workerId->${workerId} dataId->${dataId} input->${input}`); resolve({ workerId, dataId, input, result: input * 10, }); }, input * 1000); }); } (async () => { // promise間で共有するデータ const data = [4, 3, 2, 1, 0, 3, 2, 1]; // 処理するデータ let dataIdx = 0; // 処理位置 const workerNum = 3; // 同時実行数 const workers = []; for (let workerIdx = 0; workerIdx < workerNum; workerIdx++) { const p = new Promise(async (resolve, reject) => { const results: any = []; while (dataIdx < data.length) { const idx = dataIdx; dataIdx++; // awaitの前にインクリメントする const res = await task(workerIdx, idx, data[idx]); results.push(res); } resolve(results); }); workers.push(p); } // 終了待ち const res = await Promise.all(workers); // 結果取得 for (const p of res) { for (const v of (p as any)) { console.log(v); } } })();
ポイントとしては、同時実行数だけPromiseを作成して、そのPromise間で入力データを共有させるところでしょうか。
注意点としては、タスクは非同期実行されるので、タスク関数を呼び出す前に、処理位置を進めておくのを忘れずに。
感想など
今回の例は、入力データ数が決まっている場合のみ有効で、ストリームなど、データ数が未定の場合には使えません。何とかできないかと色々試してみましたが挫折しました…。