🔲
JavaScriptのイベントループ内のMacrotasksとMicrotasksについて
まずは下記コードの実行順を考えてみてほしい。
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と違う動きするというのが罠仕様な気もするが。。。
参考記事
Discussion