対象読者
-
Array<Promise<T>> => Array<T>
にPromise.all
使いたくない人(for文も使いたくない) - 順序どうり実行して欲しい非同期処理を配列で扱いたい(for文は使いたくない)
導入
次のような非同期処理を行うとします。
関数fa
はここでは200ms, 400ms, 100msかかる処理を行いその結果を配列として返します。
// msec ms後にmsesの値を返す
const sleep: (msec: number) => Promise<number>
/**
* [2, 4, 1] => [200, 400, 100]
*/
async function fa(arr: Array<number>) {
const promiseNumbers: Promise<number>[] = arr.map(async (num) => await sleep(100 * num));
const numbers: number[] = await Promise.all(promiseNumbers);
return numbers;
}
const result = await fa([2, 4, 1]);
expect(result).toEqual([200, 400, 100]);
Array<Promise<T>> => Array<T>
の変換に、普通のArray.mapを使うときは、await Promise.all
で全てを並列処理するのが一般的です。
しかし、async/await
を使って折角Promise
を使わないプログラミングをしているのにPromise.all
を使うのはちょっとだけセンスがないと思います。
別のアプローチを試してみます。
fp-ts/Task, fp-ts/Array.sequence を用いる
関数型utilライブラリであるfp-ts
を用いてsequential(順序立てて)に非同期処理を行います。
それに伴いPromise
を取り扱うTask
で非同期な値を取り扱うことにします。
export interface Task<A> {
(): Promise<A>
}
Promiseを返す関数を以下のようにラップしてTaskを返すようにしました
const sleepTask = (msec: number): T.Task<number> => async () => await sleep(msec);
sleepTask(mesc)
自体はまだ実行されず、seqence関数でArray<Promise<number>> => Promise<Array<number>>
にしたあとにsleep関数が呼ばれるようになります。
async function fb(arr: Array<number>) {
// Task<T>[] => Task<T[]> への変換
const sleepTasks: T.Task<number>[] = arr.map((num) => sleepTask(100 * num));
const sleepSeqTasks: T.Task<number[]> = A.array.sequence(T.task)(sleepTasks);
// ここでタスクの実行
const numbers = await sleepSeqTasks();
return numbers;
}
実行する順番もsequentialにする
taskのsequentialの内部実装ではPromise.allを呼んでいるのでsleep関数はすべて並列実行されます。
sleep関数内部でlogを呼ぶようにしてみます。
const sleep = async (msec: number) => {
await new Promise((resolve) => setTimeout(resolve, msec));
console.log(`run sleep(${msec})`); // sleepが完了したタイミングでログが出力される
return msec;
};
await fa([2, 4, 1]);
// run sleep(100)
// run sleep(200)
// run sleep(400)
await fb([2, 4, 1]);
// run sleep(100)
// run sleep(200)
// run sleep(400)
並列処理なので、速いもの順に出力されました。
順番に実行したいときはchain実装されているMonad型taskSeq
を利用するとよいです。
async function fc(arr: Array<number>) {
// Task<T>[] => Task<T[]> への変換
const sleepTasks: T.Task<number>[] = arr.map((num) => sleepTask(100 * num));
const sleepSeqTasks: T.Task<number[]> = A.array.sequence(T.taskSeq)(sleepTasks);
// ^ -- taskSeq にした
// ここでタスクの実行
const numbers = await sleepSeqTasks();
return numbers;
}
await fc([2, 4, 1]);
// run sleep(200)
// run sleep(400)
// run sleep(100)
配列順にログが出力されました。