🔲

JavaScriptのイベントループ内のMacrotasksとMicrotasksについて

2024/10/21に公開

まずは下記コードの実行順を考えてみてほしい。

setTimeout(() => console.log("timeout"));

Promise.resolve()
  .then(() => console.log("promise"));

console.log("code");

答えは code promise timeout の順番となる。

仮にPromiseとsetTimeoutが同じタスクに積まれるなら、順番は code timeout promise となるはず。

そうなっていないのは別のタスクに積まれるため。

タスクはMacrotasksとMicrotasksに分かれている

  • Promiseは Microtasks に積まれる
  • setTimeoutやfetch apiなどは、Macrotasks に積まれる

下記コードを実行してもらえれば、同じタスクに積まれたものはちゃんと積んだ順で処理されているのが分かるはず。

setTimeout(() => console.log("timeout1"));
setTimeout(() => console.log("timeout2"));

Promise.resolve()
  .then(() => console.log("promise1"));
Promise.resolve()
  .then(() => console.log("promise2"));

console.log("code");

Microtask 間では UI やネットワークイベントの処理が動かない

Reactで、通常時リクエストしているところに、モックを返す処理(Promiseだけ返す)を入れたらUIに反映されなかったという経緯がある。

一応、下記のように書いてMacroTasksに積んで解決した(分岐先のイベントループを揃えたとも言える)

// MicroTasksに積まれてしまうパターン(UI やネットワークイベントが動かない)
return new Promise.resolve(123)

// MacroTasksに積むパターン(UI やネットワークイベントが動く)
return new Promise((resolve) => {
  setTimeout(() => resolve(123), 0)
})

さいごに

Node.jsの話だが、バッチ処理で再帰呼び出しした際に、スタックオーバーフロー回避でsetTimeoutを入れていたが分岐先では書いておらずエラーになるとかあった。

処理が分岐した先のイベントループも同じになるように書かないと思わぬバグを踏むことになるので、そういうところにも注意して書いていくとよいと思う。

今回の場合、Promiseが非同期処理だけど、setTimeoutと違う動きするというのが罠仕様な気もするが。。。

参考記事

https://ja.javascript.info/event-loop#ref-2369

Discussion