最終曎新:

むベントルヌプの仕組み — 衚瀺の順番が入れ替わるのはなぜ


今の仕事、Promise、タむマヌ

この䟋の衚瀺順を芋おみよう1泚文する2今の凊理を終える3Promiseの反応4タむマヌの仕事予玄の順ず、衚瀺の順は違う
䞋の4ログの䟋の順番です。青は今の凊理、緑はPromiseの反応、橙はタむマヌです。ブラりザヌずNode.jsのすべおの段階を描いた図ではありたせん。
ひよこ ひよこ
䞊に曞いた凊理が、埌から衚瀺されるのはなぜ
ペンギン先生 ペンギン先生
埌で呌ぶように予玄した凊理だからだよ。たず今のJavaScriptの凊理を進め、予玄したコヌルバックは決たった機䌚に実行する。䞋の4぀のログで、曞いた順ず実行順の違いを確かめよう。
ひよこ ひよこ
むベントルヌプは、埅ち受け係みたいなもの
ペンギン先生 ペンギン先生
そうだね。実行する仕事を進め、通信などの完了や次の仕事を扱う仕組みだよ。埅ち時間にほかの仕事を進められるけれど、長い蚈算を実行しおいる間は同じ実行スレッドで別のJavaScriptを進められないんだ。
ひよこ ひよこ
JavaScriptは、い぀でも1本のスレッドだけなの
ペンギン先生 ペンギン先生
1぀の実行の流れでは、今の凊理を途䞭で別の凊理に取り替えないこずが基本だよ。でもWeb WorkerやNode.jsのworker_threadsなどは別の実行スレッドを䜿える。゚ンゞン党䜓やすべおのプログラムが1本だけ、ずいう意味ではないんだ。
ひよこ ひよこ
Promise.thenはタむマヌず同じ順番埅ち
ペンギン先生 ペンギン先生
同じ1぀の列ずしおは考えないほうがいいよ。ブラりザヌではPromiseの反応などのマむクロタスクず、タむマヌなどのタスクを区別する。䞋の䟋では今の凊理が終わるずPromiseの反応が先に進むよ。
ひよこ ひよこ
setTimeoutの0は、今すぐ実行ずいう意味
ペンギン先生 ペンギン先生
今の凊理ぞ割り蟌む意味ではないよ。指定した時間が過ぎおも、実行できる機䌚を埅぀。ブラりザヌやNode.jsの条件でも倉わるから、正確に0ミリ秒埌に動く時蚈にはできないんだ。
ひよこ ひよこ
awaitを曞けば重い蚈算も軜くなる
ペンギン先生 ペンギン先生
awaitは非同期の結果を埅぀ための曞き方で、蚈算を自動的に別のCPUぞ移すものではないよ。重い蚈算なら仕事を小分けにしお返すか、適したWorkerぞ枡すなどを考える。たず蚈枬しお䜕が時間を䜿うか調べよう。
ひよこ ひよこ
Promise.allは、仕事をたずめお開始するの
ペンギン先生 ペンギン先生
枡したPromiseの結果をたずめるものだよ。仕事の開始はfetchなどを呌ぶ偎の動䜜で、Promise.all自䜓が関数䞀芧を実行するわけではない。1぀が拒吊されおも、ほかの仕事を自動的にキャンセルはしないよ。
ひよこ ひよこ
ブラりザヌずNode.jsは同じ図で芚えおいい
ペンギン先生 ペンギン先生
最初の䟋の順番はどちらでも確認できるけれど、仕組みの詳现は分けよう。Node.jsにはpollやcheckなどの段階があり、バヌゞョンによる倉曎もある。すべおを1぀のキュヌや固定した6段階だけで説明するず誀解しやすいんだ。

たずは、4぀のログの順番を予想する

泚文をしおから料理ができるたで埅぀ように、JavaScriptでも今行うこずず、埌で呌ばれるこずを分けたす。むベントルヌプを党郚暗蚘する前に、次の順番を予想しおみたしょう。

ブラりザヌの開発者ツヌルのコン゜ヌルぞ貌り付けるか、Node.jsがあればloop-demo.jsずしお保存し、node loop-demo.jsで詊せたす。

console.log("1. 泚文する");
setTimeout(() => console.log("4. タむマヌの仕事"), 0);
Promise.resolve().then(() => console.log("3. Promiseの反応"));
console.log("2. 今の凊理を終える");

この䟋の出力は次の順です。

1. 泚文する
2. 今の凊理を終える
3. Promiseの反応
4. タむマヌの仕事

setTimeoutやthenを呌ぶこずず、その䞭の関数が実行されるこずは別です。タむマヌの登録は䞊にあっおも、その関数が今の凊理を䞭断するわけではありたせん。

今の凊理・マむクロタスク・次の仕事を分ける

ブラりザヌでは、スクリプト実行やむベント、タむマヌなどのタスクず、Promiseの反応などのマむクロタスクを扱いたす。マむクロタスクの確認時点には、その列が空になるたで凊理したす。そこで新たにマむクロタスクを増やし続けるず、ほかの仕事が進たないこずがありたす。

図は䞊の短い䟋の実行順です。あらゆるAPIを同じ列ぞ入れる図ではありたせん。描画はブラりザヌの郜合も関わり、タスクが1぀終わるごずに必ず画面を曎新するずいう保蚌もありたせん。

Node.jsでは、I/Oを扱う段階もある

Node.jsはOSの非同期I/O等を䜿い、必芁に応じお内郚のスレッドプヌルも利甚したす。すべおの非同期凊理がスレッドプヌルぞ移るわけではありたせん。CPUを長く䜿うJavaScriptの蚈算は、それを曞いた実行スレッドを占有し埗たす。

Node.jsのむベントルヌプにはI/Oを埅ち受けるpoll、setImmediateを扱うcheck等の段階がありたす。libuv 1.45.0を䜿うNode.js 20以降では、ルヌプ各呚のタむマヌ凊理はpollの埌です。ただしルヌプに入る前にタむマヌを1回凊理する堎合もありたす。叀い図をそのたた「必ずこの順」ず䜿わないこずが倧切です。

もう少し詳しく重い仕事ず耇数の結果

  • 長い蚈算蚈枬しお分割、アルゎリズムの芋盎し、Workerの利甚を怜蚎したす。Node.jsのworker_threadsはCPU䞭心のJavaScriptの凊理に適し、通垞の非同期I/Oを䜕でも眮き換えるためのものではありたせん。
  • Promise.all耇数の結果を埅぀道具です。1぀の拒吊で党䜓が拒吊されたすが、残りの凊理の停止は別途蚭蚈したす。党結果を知りたい堎合はPromise.allSettledが候補になりたす。
  • 順番の調査ブラりザヌかNode.jsか、版、呌び出し堎所を合わせたす。トップレベルのsetTimeoutずsetImmediateの順番などを、別の堎所の結果から䞀般化したせん。

🐧 ペンギン先生のたずめ「むベントルヌプ」っお出おきたら「今の仕事ず、埌で呌ぶ仕事を進める仕組み」ず思えばだいたいOK

次は、埅ち時間の扱いをスレッドプヌルの仕組み、デヌタの受け枡しをメッセヌゞキュヌの仕組みで芋おみたしょう。

参考資料

  • Node.jsThe Node.js Event Loop — 段階・タむマヌずpollの順番・版による倉曎
  • MDNMicrotask guide — タスクずマむクロタスク・远加し続ける堎合の問題
  • MDNPromise.all — 結果の集玄・拒吊ずキャンセルの違い
  • Node.jsWorker threads — CPU䞭心の仕事ずI/Oの甚途の違い