JavaScriptの実行キューの感覚を掴む

悩みの種であるJavaScriptの非同期処理について、感覚を掴めたような気がするので備忘録です。Qiitaの記事が非常にわかりやすかったので参照します。

JavaScriptの同期、非同期、コールバック、プロミス辺りを整理してみる – Qiita

JavaScriptの実行については次のようなことが言えるようです。

  • JavaScriptはシングルスレッド
  • 実行キューに登録された関数が順番に実行される
  • 実行キューに登録される順番は同期的なものと非同期的なものがある

排便で例えてみます。うんちする、トイレを流す、パンツを履くの3つの動作を実行する関数で表しています。ただし、うんちをするのは非常に時間のかかる処理なので、パフォーマンス重視のために非同期関数となっています。(setTimeoutを使って非同期処理にしています。)

// unchi.js
const fireUnchi = () => {
    setTimeout(() => {
        console.log('うんちしました。');
    }, 0)
}

const flushToilet = () => {
    console.log('トイレを流しました。')
}

const wearPants = () => {
    console.log('パンツを履きました。')
}



fireUnchi();
flushToilet();
wearPants();

実行すると次のようになります。

$ node unchi.js
トイレを流しました。
パンツを履きました。
うんちしました。

悲劇です。setTimeoutは0秒なので即座に実行されても良さそうですが、この挙動は同期処理と非同期処理実行キューへの登録する方法を掴むと理解しやすいです。

同期処理では3つの関数を順番に実行していますが、最初のfireUnchi関数が実行されたときはsetTimeoutのタイマーが登録されています。setTimeoutの第二引数には0が指定されているので0秒=即座に実行キューに登録されそうですが、同期的な処理ではタイマー登録だけです。flushToiletwearPantsが実行キューに登録されたのでlogが表示され、同期的な処理が終了した段階で次に非同期処理が始まります。タイマー指定は0秒なので非同期処理に移ってから0秒後にfireUnchiは実行キューに登録されます。

つまり、0秒後だろうが1分後だろうが、同期的な処理が終了してからタイマーのカウントがスタートするので同期処理より前になることはないということですね。

非同期処理を同期的に扱う方法はcallback関数やES6から追加されたPromiseを使う方法などがありますが、それはまた別に書こうと思います。