はじめに
駆け出しだった頃の話を供養します。
JavaScript(Vue.js)を業務で使い始めて、
きっかり1年ぐらい経とうとしてた目駆け出しエンジニアでそた。
私が担当している某製品のUIはWebの操作に対応していて、とても評判がいいのですが、
ふとネットワークを確認すると定期受信がなんかうまく行ってない。
しかもページによってポーリング間隔がバラバラ…。なんで???
結論
結論から書くとsetTimeout…というよりもJavaScriptの非同期処理の理解不足が原因でした。
APIに対して1秒間隔でRequestを出していくシステムを想定してください。
想像していた処理

詳しい人から見ると一瞬で否定されそうな図ですが、割と本気でこう考えていて、
Fetchさえ1秒以内に間に合えば、常に1秒で実行されるかと思っていました…。
実際の処理

超絶当たり前なのですが、 DOMの操作のためにJavaScriptを使用するので
大雑把ですが、こんな感じの図になるはずです。
こうなると、Renderの時間が長すぎて1秒間隔でリクエストを送ることができません。
…なるほど。ん?
JavaScriptってシングルスレッド処理なの?
非同期処理が可能なシングルスレッド処理
ここからは下記が一番私には理解しやすかったですが、もっと噛み砕いて解説していきます。
mercari engineering - JavaScriptがブラウザでどのように動くのか
Javascriptはシングルスレッドで非同期処理をするためにAPIを使用します。
APIとは一般的にはYouTubeやXの情報を取得するやつですが、JavaScript内部でも存在します。
ブラウザー API はウェブブラウザーに組み込まれていて、ブラウザーやコンピューターの環境の情報を取得し、これを使って役に立つ複雑なことを行えるようにするものです。 例えば ウェブオーディオ API は、ブラウザー内での音声の操作、たとえば音声トラックの取得、音量の変更、エフェクトの適用などを行うための JavaScript の仕組みを提供します。実際には、裏でブラウザーは低レベル(例えば C++ や Rust)の複雑なコードを使って実際の音声処理を行います。しかし、この複雑さを API が抽象化して隠蔽します。
PythonでもCPythonなるものがあるようにコンパイラ標準のインターフェースが搭載されております。
JavaScriptの非同期処理は一旦、メインのランタイムとは切り離して処理されます。
ここで以下の例で考えてみましょう。
function main(){
request();
fetch();
render();
}
// 一秒ずつmain関数を3回実行
main(); // main(1)
setTimeout(main, 1000); // main(2)
setTimeout(main, 2000); // main(3)
ブラウザAPIについて
先程説明の重複ですが、ブラウザ標準に搭載されているAPIです。
今回の場合はsetTimeoutを実現するために使用しています。
ブラウザAPIにmain(2)、main(3)をそれぞれ1秒後、2秒後にタスクキューに格納します。
タスクキューについて
タスクキューは FIFO (First In, First Out) 方式で、格納した順に取り出しが行なわれます。
コールスタックキューが空になったらコールスタックで処理を実行します。
コールスタックについて
コールスタックは実際に処理をする場所になります。
この場合はmain(1)、main(2)、main(3)関数を実行しています。
結局何が起こってたの?
コールスタックが1秒以上も占有されていたorz
イベントループ
上記の画像のように、ブラウザAPIから投げられた関数の処理をキューして処理をしています。ざっくり言えば、このようにシングルスレッドでブラウザ内の各領域を関数がループしていることを イベントループ と呼びます。イベントループで大事なのは、コールスタックが空にならないと次の処理が実行できないことです。
まとめ
今回の場合、常に一定間隔でポーリングをしているつもりが、コールスタックに阻まれて中々、実行できなかったのが原因でした。こうして書いてみるとごく当たり前の話なのですが、駆け出しの頃はJavaScriptをよく知らずVue.jsを使用していたため、非同期処理をあまり意識できず凄く困惑しました。やはりフレームワークを扱うのは初心者にはうってつけですが、こうした落とし穴にハマってしまいやすいですね・・・。
