配列のPromiseを直列で実行する方法

2021/05/04に公開1

始めに

配列のPromiseを並列で実行する場合はPromise.allを使うと思いますが、直列で順番に実行するにはどうするか少し悩みました。パッと調べたときにあんまりいい情報が見つからなかったのでまとめてみました。

並列実行
const waitTimes = [300, 100, 200, 500, 400];

function wait(waitTime: number) {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log(`${waitTime}ms waited.`);
      resolve();
    }, waitTime);
  });
}

(async () => {
  // Promise.allで並列に実行される
  await Promise.all(
    waitTimes.map(async (waitTime) => {
      await wait(waitTime);
    })
  );
})();

Promiseを直列で実行する方法

最も単純な方法(forループ)

身も蓋もない話ですが、forループ内でawaitを書くで順番に実行することが出来ます。
ただ最近mapfilterなど配列操作系のメソッドを使い続けていてあまりforループは使いたくなかったので、別な方法も考えました。

単純な方法で直列に実行する
(async () => {
  for (let i = 0; i < waitTimes.length; i++) {
    await wait(waitTimes[i]);
  }
})();

配列操作系のメソッドでの実装(reduceによる畳み込み)

Promiseは以下のように.then()をつないでいくことで順番に実行していくことができます。

Promise.resolve()
  .then(() => wait(100))
  .then(() => wait(200));

これを上手く配列操作系のメソッドでやる場合はreduceを使用します。第2引数にPromise.resolve()を渡してresolveされたPromiseを初期値として与え、毎回promise.then()を実行して返すことで繋げることができます。

reduceによる直列実行
(async () => {
  await waitTimes.reduce((promise, waitTime) => {
    return promise.then(async () => {
      await wait(waitTime);
    });
  }, Promise.resolve());
})();

終わりに

以上がPromiseを直列で実行する方法でした。個人的には配列操作系のメソッドを使った方が全ての要素に同じ処理をする意図がハッキリしているのでこちらの方を使いたいのですが、今回のケースだと結構複雑になって悩ましいなと思いました。forループの方が圧倒的に分かりやすいんですよね。。カッコつけたい人は是非reduceの方を使ってみてください(笑)
今回のサンプルコードは以下で確認できるので、興味があるかたは見てください。

https://www.typescriptlang.org/play?#code/MYewdgzgLgBA7gQwJZQCpILYFMIwLwwDaAzAAykA0MAjOVQEx0wCsTALOQLoDcAULwDMArmGBQk4eMigAKRCnTYAXDDBCMAIywAnAJQwA3rxgxtWKEO1hVWODAAK2kBiQQsAHgBuIJABMAfDIyZhAgADaeWPp4-obGJjBuaJhYIEKyMtGxRgkJoJDhWAB0YSAA5jIABgAkBvLJ2AC+GLj1WL5Flbp8uSYh4ZGZPQmNVPWKUcON3byN-DIIEACeojCZ+NnxAPRbMIBkcoDoSoD52oAyEfH5oWHFpRUA5AAOCNoIYVdhMAhiEpBFtzMmCHqDicLjcRReYRk8RM4xSECKGAQ9wWy1WcmkEyycV6HyB9XRChS-xGuniM22u0AL27HM4mC6FErlGS3NzaJAvD5fcDwv7DAQgbRrK6wJD4GCkbgwUXuKSE7Dwq5gMpQAAWkqQAGoNfocglAdJZbJYfLCEhOMS5uduQybszWez3p9xNz6L9ifqUIaJvCzL4hMAsEF7iDXFgxhiiRtsQkzBYrDBg85Q0VVVgwCiVsA1ljdbkPbB8cbJtCYNMplRHEmwf0IoHdDNpkN4kA

Discussion

standard softwarestandard software

次のように書いても、
300ms waited.
100ms waited.
200ms waited.
500ms waited.
400ms waited.
の順番で動作しました。
いろんな書き方ができるみたいです。

(async () => {
  await (async () => {
    for (const waitTime of waitTimes) {
      await wait(waitTime);
    }
  })();

  await (() => {
    let i = 0;
    const runThen = (p) => {
      if (waitTimes.length <= i) { return; }
      i += 1;
      p.then(() => runThen(wait(waitTimes[i])));
    }
    runThen(wait(waitTimes[0]));
  })();
})();