https://developer.mozilla.org/ja/docs/Web/API/HTML_DOM_API/Microtask_guide
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Execution_model
Promise はコールバックを呼び出す際にマイクロタスクを経由する。検証用に以下のコードを実行してみる。
setTimeout(() => console.log("timeout"), 0);
new Promise((resolve) => {
console.log("promise");
resolve();
}).then(() => {
console.log("promise then");
});
console.log("main");すると次のような実行結果が得られる。
promise
main
promise then
timeout
ここで登場する 4 つの console.log は同期的・マイクロタスク・マクロタスクの 3 つの方法で呼ばれている。「同期的」というのはコールスタックで上に乗っている、すなわち直接呼び出されているという意味。処理の順番を詳細に表してみる。
setTimeoutがコールバックを登録する- ランタイムがどこかのタイミングで「0 秒後」にコールバックをイベントキューに突っ込む (A)
Promiseコンストラクタが実行される- このときコンストラクタに渡したコールバックが実行される
console.log("promise")が呼び出されるresolve()が呼び出されるthenコールバック関数がマイクロタスクに登録される (B)
- このときコンストラクタに渡したコールバックが実行される
console.log("main")が実行される- マイクロタスクに登録されている (B) のコールバックが実行される
console.log("promise then")が呼び出される
- イベントキューの先頭にあるイベント (A) のコールバックが実行される
console.log("timeout")が呼び出される
dispatchEvent はイベントリスナを同期的に呼び出します。以下のような振る舞いです。
class EventTarget {
dispatchEvent(event: Event) {
for (const listener of this.listeners[event.type]) {
listener.call(event);
}
}
}