JavaScript
async
Recursive
await

非同期処理を待ちながら再帰する[JavaScript]

まずソースコードから

// 0~5 までの乱数生成するだけのヤツ
const genRandom = () => {
  const random = Math.floor(Math.random() * 5);
  console.log('generate: ', random);
  return random;
};

// 非同期な処理
const asyncFunc = (delay, index) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(index);
    }, delay * 1000);
  });
};

// 非同期な処理を待ちながら再帰するヤツ
// リトライ回数を受け取る
const asyncRecursion = async (retry) => {
  const result = await asyncFunc(genRandom(), retry);
  console.log('result: ', result);

  if (retry > 0) await asyncRecursion(--retry);

  return true;
}

// はじまり
asyncRecursion(10).then(() => {
  // 全部が終わったらここにくる
  console.log('finished.');
});

動くやつ

https://jsfiddle.net/5nj0htfg/

何がしたかったのか

非同期な処理の結果を待って、その結果を判定しNGだったらリトライする処理を書きたかった。それだけ。
要は通信のリトライとかそういうイメージ。

結果はどうなるのか

generate:  1
(ここで1秒待ってから result が出力される)
result:  10
generate:  0
result:  9
generate:  2
result:  8
generate:  1
result:  7
generate:  1
result:  6
generate:  1
result:  5
generate:  0
result:  4
generate:  2
result:  3
generate:  3
result:  2
generate:  3
result:  1
generate:  0
result:  0
finished.

result がちゃんと 1 ずつデクリメントされて出力されているので、それぞれの非同期処理を待って再帰していることがわかる。

実際どう使うのか

genRandom はテスト用にランダムな遅延時間入れたかっただけなのでどうでもよろしい。
asyncFunc はリトライしたい非同期な処理の実体。

ということで例えばこういう感じ。

const asyncRecursion = async (retry) => {
  const response = await fetch('https://example.com/');
  if (!response.ok && retry > 0) {
    await asyncRecursion(--retry);
  }
  return response.json();
}

asyncRecursion(3).then((json) => {
  // レスポンス受け取ったあとにやりたいこと
});

通信を初回 + 3回リトライする。

所感

コード書いてるときは「おっ?これおもしろいじゃん」なんて思ってたけど、ここに書くためにまとめてると「なんだ普通のことじゃん」ってなった。
本当にやりたかったのは、とある非同期通信によって更新される入力欄を setInterval で値監視している非同期処理を待つとかいう面倒くさいものだったので、そっちの面倒くささに引きずられてリトライ処理まで面倒くさいものだと思い込んでしまった感がある。
とはいえシンプルに書けて満足している。