🍎

async/await理解のためのクイズを考えてみた (前半)

に公開

はじめに

今私は、JSの非同期処理を100%理解したいと思い、以下の学習ロードマップを参考にして学習を進めています。
JSの非同期処理を理解するために必要だった知識と学習ロードマップ

今回は、その学習ロードマップの中の「Promise チェーンの構築のアンチパターンを学ぶ」という章で紹介されていた以下の動画に取り組んでいきます。
JavaScriptのasync/await 理解してますか? 説明できますか? クイズに答えてもらって良いですか?

今回は、動画の前半くらいまでのアンチパターンを修正するというところ考えてみます。

アンチパターン

以下のコードを正しい書き方にしろという問題です。

//node14
const test = async() => {
    console.log(1);
    await setTimeout(() => {
        console.log(2);
    }, 1000)
}

const main = async() => {
    await test();
    console.log(3);
}

main();

何が問題か

答えを聞く前に自分で考えてみた記録を書きます。

私の予想

1
3
2
  • mainという関数が呼ばれる
  • main関数の中でtest関数をawaitで呼ぶ。(このtest関数の結果を待って、次の処理に行く)
  • 1が出力
  • setTimeoutの処理が始まるが、非同期コールバック関数であるため、この処理の結果はまだ出力せずに、他の処理を実行させる。
  • test関数の3を出力
  • setTimeoutの処理の3が出力

でも引っかかるのが、main関数はtest関数の結果を待つはずだが、test関数内のsetTimeoutの処理が終わってない。

回答

予想は合っていた。

1
3
2

やはり、setTimeoutにawaitをつけたことによって、他の処理に実行終了を待って、setTimeoutの処理が行われるみたい。 (👉のちにこの解釈は誤解があることがわかる)
node14では、setTimeoutにawaitをつけるべきではないとあるらしい。

修正したらどうなる?

1,2,3に出力がなるように、修正します。

私の予想

いくつか予想を立ててみた
1つ目

//node14
const test = function () {
    console.log(1);
    setTimeout(() => {
        console.log(2);
    }, 1000)
}

const main = async() => {
    await test();
    console.log(3);
}

main();

ただsetTimeoutからawaitを抜いただけ。setTimeoutは非同期コールバック関数で、task queueに一度入るよね。でもasync awaitは多分、microtask queueに入る非同期コールバック関数だから、setTimeoutよりも実行が優先されて、1, 3, 2になるのか…?

自分の環境で実行してみたら、出力がやっぱり、1,3,2になった。

てことは、同期コールバック関数をsetTimeoutの代わりに書けばいいのか…?

//node14
const test = function () {
    console.log(1);
    console.log(2);
}

const main = async() => {
    await test();
    console.log(3);
}

main();

それか

//node14
const array = [1, 2]
const test = function () {
    array.forEach((element) => console.log(element));
}

const main = async() => {
    await test();
    console.log(3);
}

main();

これでいいのかって感じだけど、同期コールバック関数に置き換えたことによって、出力は1,2,3になった。でも非同期処理の本質的な問題からはズレている気がする。
さあ、正解を見てみよう

回答

// node14
const test = async () => {
    console.log(1);
    await new Promise((resolve) => {
        setTimeout(() => {
            console.log(2);
            resolve();
        }, 1000);
    })
}

const main = async() => {
    await test();
    console.log(3);
}

うわあ、ここでPromise使うんかあ…。わかってたつもりだったのに悔しい〜。

なんでこれが正解なのか考えてみる

Promise処理を書いていて、その中にsetTimeout関数がある。このPromise処理では結果をresolveで伝えている。その結果を待って、3が出力されているということか。

まとめ

  • awaitは、Promiseだけを待機できる。
    👉 ここ知ってたようで、知らなかった。なんでも待てるのかと思ってた。
  • setTimeoutは、Promiseを返さないから、test関数内で実行はされるけど、awaitでは止まらない。

Discussion