JavaScriptはシングルスレッドで実行される


自分自身が勘違いしていたので記事にします。

問題

<!DOCTYPE html>
<html>

<head>
    <title>Document</title>
</head>

<body>
    <script>
        console.log(`Start ${new Date()}`)

        // 3秒後にCallbackと表示
        setTimeout(() => {
            console.log(`Callback ${new Date()}`)
        }, 3000)

        // 10秒間かかる処理を実行する
        const now = new Date()
        let dummy = 0
        while (new Date() - now < 10000) {
            dummy++ // 適当な計算処理
        }

        console.log(`End ${new Date()}`)
    </script>
</body>

</html>

このHTMLをブラウザで表示させると、コンソールにはStart, Callback, Endがどの順番で表示されるでしょうか?

  1. Startが表示される
  2. 3秒後にCallbackと表示されることが予約される
  3. 10秒間かかる処理を実行する
    この間に少なくとも3秒間経過するのでCallbackと表示される
  4. Endと表示される

よってStartCallbackEnd

だと思っていませんか?

残念!不正解です。

実行結果

Start Tue May 21 2019 10:39:40 GMT+0900 (日本標準時)
End Tue May 21 2019 10:39:50 GMT+0900 (日本標準時)
Callback Tue May 21 2019 10:39:50 GMT+0900 (日本標準時)

実際にはStartEndCallbackの順番で実行されます。

ポイント

ここら辺がポイントです。

  • setTimeoutは規定時間後に関数の実行を予約する
  • しかし、JavaScriptはシングルスレッドで実行される
    • つまり、2つ以上の処理を並行して実行できない
    • 2つ以上の関数を同時実行できない

解説

つまり、正しくは以下のような順序になります。

  1. Startが表示される
  2. 3秒後にCallbackと表示されることが予約される
  3. 10秒間かかる処理を実行する
    この間に少なくとも3秒間経過するが、まだメインの処理が実行されているので2.の関数を実行開始できない
  4. Endと表示される
  5. メインの処理が完了して次の処理を実行可能になったので、2.の関数が実行される
    Callbackと表示される

よってStartEndCallbackの順番で実行されます。

ちなみに、setTimeoutを使う場合以外でも、

  • Ajaxでの非同期処理
  • Node.jsでのファイル読み込みなどの非同期処理

などの場合もシングルスレッドなので同様の処理順になります。