3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Promise.allを書きたくない人向け

Last updated at Posted at 2020-09-10

対象読者

  • 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) 

配列順にログが出力されました。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?