async/awaitとreduceの使い方

詰まったのでメモ。

同期

以下のように書くと

[1, 2, 3].reduce((acc, v) => acc + v, 0);

//6

「1,2,3という配列の要素を1つずつ順番に足していく」という処理になります。

これは超基本的なので、すんなり理解できる😀

非同期

以下のように書くと

(async () => {
  const sleep = (n) => new Promise((resolve) => setTimeout(resolve, n));

  [1, 2, 3].reduce(async (acc, v) => {
    await sleep(1000);
    console.log(v);
  }, 0);
})();

//出力結果
//1
//2
//3

「1,2,3という配列の要素を1秒のインターバルを挟んで順番に表示していく」という処理になるはずなので、順番に1,2,3と1秒ずつ間隔をあけて表示されていくと思いきや、実際は1,2,3が同時に表示されます。(ブラウザで実行してみてください)

一体なぜ・・・?🤔

こうなる理由は、同期処理だとreduceは以下のような動きになるけど

  • 1周目:すぐにスタートする
  • 2周目:1周目の終了を待ってからスタートする
  • 3周目:2周目の終了を待ってからスタートする

 

非同期処理にすると

  • 1周目:すぐにスタートする
  • 2周目:1周目の終了を待たずにスタートする
  • 3周目:2周目の終了を待たずにスタートする

という動きになってしまうため。

 

これを阻止するためには、以下のように処理の最初にawait accを追加します😎

(async () => {
  const sleep = (n) => new Promise((resolve) => setTimeout(resolve, n));

  [1, 2, 3].reduce(async (acc, v) => {
    await acc;
    await sleep(1000);
    console.log(v);
  }, 0);
})();

//出力結果 
//1 
//2 
//3

これを追加することで

  • 1周目:
    →すぐにスタートする
  • 2周目:
    →1周目の終了を待たずにスタートするが、最初にawait accと書かれているので、1周目の結果が返ってくるまでストップする
  • 3周目:
    →2周目の終了を待たずにスタートするが、最初にawait accと書かれているので、2周目の結果が返ってくるまでストップする

という動きになります。なので順番に1,2,3と1秒ずつ間隔をあけて表示されていきます。

 

このように「1周目の終了を待たずに2周目がスタートする」という処理になるのは、reduceに限らず

  • foreach
  • filter
  • map

なども同じ。

 

逆に

  • for
  • for of
  • for in

などはasyncを使っても「1周目の終了を待ってから2周目をスタートする」という感じで同期的に実行できるっぽい。

足し算

最後に、非同期reduceを使った足し算バージョンを書いて終わりにします。

(async () => {
  const sleep = (n) => new Promise((resolve) => setTimeout(resolve, n));

  const add = await [1, 2, 3].reduce(async (acc, v) => {
    await acc;
    console.log(v);
    await sleep(1000);
    return (await acc) + v;
  }, 0);

  console.log(add);
})();

//実行結果
//1
//2
//3
//6

このように書くと、順番に1,2,3と1秒ずつ間隔をあけて表示されていき、最後に6と表示されます。

  • return (await acc) + v;の部分は、return acc + v;と書いてはダメ。
  • awaitは「渡されたPromiseが解決されるまで待ち、解決後に値を返却する」ものなので、今回の場合でいうと元々のaccを上書きはしない。
  • なので、「最初にawait acc;と書いているので、accはすでに解決済みでしょ?」と思いきや、その返却値はどこにも利用されずに消えているので、その後にreturn acc + v;と書いたとしても、その中のaccは未解決のaccのままになる。なのでreturn文でもawaitが必要。
    (もしくは最初のawait acc;の返り値を利用する)

 

おわり

HTML/CSS/JavaScript
スポンサーリンク
この記事を書いた人
penpen

1991生まれ。
2019年くらいからフロントエンドエンジニアを目指している元アフィリエイターです💩

penpenをフォローする
penpenをフォローする
penpenメモ

コメント

タイトルとURLをコピーしました