ノードjsのイベントループ

Nodojsnoibentorupu



Node.js は、ユーザーがブラウザの外部のサーバー上で Javascript コードを実行できるようにする強力な Javascript フレームワークです。これは、信頼性が高く、スケーラブルな Web アプリケーションを構築するための、ノンブロッキングのイベント駆動型ランタイム環境です。イベント ループは Node.js の重要な部分で、タスクの終了を待たずに別のタスクを開始できるようにします。

Javascript はシングルスレッド言語ですが、Node.js はタスクをオペレーティング システムに割り当てることができ、複数のタスクを同時に処理できるようになります。オペレーティング システムの操作はマルチスレッドであるため、いくつかのタスクを同時に完了する必要があります。各操作に関連付けられたコールバックはイベント キューに追加され、指定されたタスクの完了時に実行されるように Node.js によってスケジュールされます。

効率的で信頼性の高い Node.js コードを作成するには、ユーザーはイベント ループをしっかりと理解する必要があります。また、パフォーマンスの問題を効果的にトラブルシューティングするのにも役立ちます。 Node.js のイベント ループによりメモリが節約され、それぞれの処理が完了するのを待たずに複数の処理を同時に行うことができます。 「非同期」という用語は、受信リクエストをブロックせずにバックグラウンドで実行される Javascript 関数を指します。







イベント ループに直接進む前に、JavaScript プログラミング言語のさまざまな側面を見てみましょう。



非同期プログラミング言語としての Javascript

非同期プログラミングの概念を見てみましょう。 Javascript は Web、モバイル、デスクトップ アプリケーションで使用されますが、JavaScript はシングルスレッドの同期コンピューター プログラミング言語であることに注意してください。



概念を理解するために、簡単なコード例を示します。





関数メソッド1 ( {

コンソール。 ログ ( 「機能1」

}

関数メソッド2 ( {

コンソール。 ログ ( 「機能2」

}

方法1 (

方法2 (

このコードでは、2 つの単純な関数が作成され、method1 が最初に呼び出され、最初に method1 をログに記録してから次の関数に移動します。

出力



同期プログラミング言語としての Javascript

Javascript は同期プログラミング言語であり、一度に 1 行だけを実行して、上から下に向かって各行を段階的に実行します。上記のコード例では、最初にメソッド 1 がターミナルに記録され、次にメソッド 2 が記録されます。

ブロッキング言語としての JavaScript

JavaScript は同期言語であるため、ブロック機能があります。進行中のプロセスを完了するのにどれだけ時間がかかるかは関係ありませんが、前のプロセスが完了するまで新しいプロセスは開始されません。上記のコード例では、method1 に 10 秒または 1 分の時間がかかったとしても、method1 のコードがすべて実行されるまで、method2 は実行されないという大量のコード スクリプトがあるとします。

ユーザーはブラウジング中にこれを経験したことがあるかもしれません。 Web アプリケーションがバックエンドのブラウザーで実行されると、膨大なコードの塊が実行されるため、ユーザーに制御アクセスが返されるまで、ブラウザーはしばらくフリーズしたように見えます。この動作はブロッキングとして知られています。ブラウザは、現在のリクエストが処理されるまで、それ以上の受信リクエストを受け入れることができません。

JavaScript はシングルスレッド言語です

JavaScript でプログラムを実行するには、スレッド機能が使用されます。スレッドは一度に 1 つのタスクのみを実行できます。他のプログラミング言語はマルチスレッドをサポートし、複数のタスクを並行して実行できますが、JavaScript にはコード スクリプトを実行するためのスレッドが 1 つだけ含まれています。

Javascriptで待機中

このセクションの名前から明らかなように、先に進むにはリクエストが処理されるまで待つ必要があります。待機には数分かかる場合があり、その間はそれ以上のリクエストは受け付けられません。コード スクリプトが待機せずに続行すると、コードでエラーが発生します。一部の機能は、コードを非同期にするために Javascript、より具体的には Node.js で実装されます。

Javascript のさまざまな側面を理解したところで、いくつかの簡単な例によって同期と非同期を理解しましょう。

JavaScript でのコードの同期実行

同期とは、コードが順番に実行されること、またはより簡単に言うと、先頭から開始して 1 行ずつ下に向かってステップバイステップで実行されることを意味します。

以下に、理解を助ける例を示します。

// アプリケーション.js

コンソール。 ログ ( '1つ'

コンソール。 ログ ( '二'

コンソール。 ログ ( '三つ'

このコードには、それぞれ何かを出力する 3 つの console.log ステートメントがあります。まず、コンソールに「One」を出力する最初のステートメントがコール スタックに 1 ミリ秒 (推定) 送信され、その後端末に記録されます。その後、2 番目のステートメントがコール スタックにプッシュされ、前のステートメントから 1 つ追加された時間は 2 ミリ秒になり、コンソールに「Two」が記録されます。最後に、最後のステートメントがコール スタックにプッシュされます。現時点では時間は 3 ミリ秒であり、コンソールに「Three」と記録されます。

上記のコードは、次のコマンドを呼び出すことで実行できます。

ノードアプリケーション。 js

出力

機能については上記で詳しく説明されており、それを考慮に入れることで、出力は瞬く間にコンソールにログインされます。

JavaScript でのコードの非同期実行

次に、コールバックを導入し、コードを非同期にして、同じコードをリファクタリングしてみましょう。上記のコードは次のようにリファクタリングできます。

// アプリケーション.js
関数 printOne ( 折り返し電話 {
setTimeout ( 関数 ( {
コンソール。 ログ ( '1つ' ;
折り返し電話 ( ;
} 1000 ;
}
関数 printTwo ( 折り返し電話 {
setTimeout ( 関数 ( {
コンソール。 ログ ( '二' ;
折り返し電話 ( ;
} 2000年 ;
}
関数 printThree ( {
setTimeout ( 関数 ( {
コンソール。 ログ ( '三つ' ;
} 3000 ;
}
コンソール。 ログ ( 「プログラムの始まり」 ;
プリントワン ( 関数 ( {
プリントツー ( 関数 ( {
プリントスリー ( ;
} ;
} ;
コンソール。 ログ ( 「番組終了」 ;

上記のコードでは次のようになります。

  • 「One」、「Two」、「Three」を出力する 3 つの関数が宣言されており、各関数にはコードを順次実行できるコールバック パラメーターがあります。
  • タイムアウトは setTimeout 関数を使用して設定され、特定の遅延後に出力するための console.log ステートメントがあります。
  • プログラムの開始と終了を示す「プログラムの開始」と「プログラムの終了」という 2 つのメッセージが表示されます。
  • プログラムは「プログラムの開始」を印刷することで開始され、その後 printOne 関数が 1 秒の遅延で実行され、次に printTwo 関数が 2 秒の遅延で実行され、最後に printThree 関数が 3 秒の遅延で実行されます。遅れ。
  • プログラムは、One、Two、Three を出力する前に「プログラムの終了」ステートメントを記録する setTimeouts 関数内の非同期コードの実行を待機しません。

出力

ターミナルで次のコマンドを実行して、上記のコードを実行します。

ノードアプリケーション。 js

これで、ターミナルの出力は次のように非同期的に表示されます。

同期実行と非同期実行について完全に理解したので、Node.js のイベント ループの概念を固めてみましょう。

Node.js: イベントループメカニズム

同期タスクと非同期タスクの実行は、Node.js のイベント ループによって管理されます。Node.js プロジェクトが起動するとすぐに実行が呼び出され、複雑なタスクがシステムにスムーズに転送されます。これにより、他のタスクがメインスレッドでスムーズに実行できるようになります。

Node.jsのイベントループの視覚的な説明

Node.js では、イベント ループは連続的で半無限です。イベント ループは Node.js コード スクリプトの開始によって呼び出され、非同期 API 呼び出しと processs.Tick() の呼び出しを担当し、タイマーをスケジュールしてからイベント ループの実行を再開します。

Node.js では、次の 5 つの主要なタイプのキューがコールバックを処理します。

  • 一般にミニヒープとして知られる「タイマー キュー」は、「setTimeout」および「setInterval」に関連付けられたコールバックの処理を担当します。
  • 「fs」や「http」モジュールのような非同期操作のコールバックは、「I/O キュー」によって処理されます。
  • 「Check Queue」にはNode固有の「setImmediate」関数のコールバックが含まれています。
  • 「Close Queue」は、非同期タスクの終了イベントに関連付けられたコールバックを管理します。
  • 最後に、「マイクロ タスク」キューには 2 つの異なるキューがあります。
    • 「nextTick」キューには、「process.nextTick」関数に関連付けられたコールバックが含まれています。
    • 「Promise」キューは、ネイティブ Promise に関連するコールバックを制御します。

Node.js のイベント ループ機能

イベント ループは、コールバックの実行順序を制御する特定の要件の下で機能します。ユーザーの同期 Javascript コードはプロセスの開始時に優先されるため、イベント ループはコール スタックがクリアされたときにのみ開始されます。次の実行シーケンスは構造化されたパターンに従います。

マイクロタスク キュー内のコールバックに最も高い優先順位が与えられ、次に nextTick キュー内のタスクの実行に移り、続いて Promise キュー内のタスクが実行されます。その後、タイマーのキュー コールバック内のプロセスが処理され、各タイマー コールバックの後にマイクロタスク キューが再度アクセスされます。 I/O、チェック、クローズ キュー内のコールバックは、各フェーズの後にアクセスされるマイクロタスク キューと同様のパターンで実行されます。

処理するコールバックがさらにある場合、ループは実行を続けます。コード スクリプトが終了するか、処理するコールバックが残っていない場合、イベント ループは効率的に終了します。

イベント ループについて深く理解したところで、その機能を見てみましょう。

Node.jsのイベントループの特徴

主な機能は次のとおりです。

  • イベントループは無限ループであり、タスクを受信するとすぐに実行を続け、タスクがない場合はスリープモードに入りますが、タスクを受信するとすぐに機能し始めます。
  • イベント キュー内のタスクは、スタックが空の場合、つまりアクティブな操作がない場合にのみ実行されます。
  • コールバックとプロミスはイベント ループで使用できます。
  • イベント ループは抽象データ型キューの原則に従っているため、最初のタスクを実行してから次のタスクに進みます。

イベント ループ、および非同期および同期実行のロジックを徹底的に理解した後、さまざまなフェーズを理解することで、イベント ループの概念が固まる可能性があります。

Node.js イベント ループのフェーズ

前述したように、イベント ループは半無限です。多くのフェーズがありますが、一部のフェーズは内部処理に使用されます。これらのフェーズはコード スクリプトには影響しません。

イベント ループは Queue の機能に従い、先入れ先出しの原則に基づいてタスクを実行します。スケジュールされたタイマーは、期限が切れるまでオペレーティング システムによって処理されます。期限切れになったタイマーは、タイマーのコールバック キューに追加されます。

イベント ループは、タスクがなくなるか、タスクの最大許容数に達するまで、タイマーのキュー内のタスクを 1 つずつ実行します。以下のセクションでは、イベント ループの中心となるフェーズについて説明します。

タイマーフェーズ

Node.js には、将来実行される関数をスケジュールできるタイマー API があります。割り当てられた時間が経過すると、タイマー コールバックはスケジュール可能になり次第実行されます。ただし、オペレーティング システム側または他のコールバックの実行により遅延が発生する可能性があります。

タイマー API には 3 つの主要な関数があります。

  • setTimeout
  • セット即時
  • セット間隔

上記の機能は同期的です。イベント ループのタイマー フェーズのスコープは、setTimeout 関数と setInterval 関数に制限されています。 check 関数は setImmediate 関数を処理します。

理論的な部分を固めるために、簡単な例を考えてみましょう。

// アプリケーション.js

関数遅延関数 ( {

コンソール。 ログ ( 「遅延関数はタイムアウト後に実行されます」 ;

}

コンソール。 ログ ( 「プログラムの始まり」 ;

setTimeout ( 遅延関数、 2000年 ;

コンソール。 ログ ( 「番組終了」 ;

このコードでは:

  • プログラムは、「プログラムの開始」というステートメントを端末に記録することによって開始されます。
  • 次に、2ms のタイマーを使用して遅延関数が呼び出されます。コード スクリプトは停止せず、バックグラウンドで遅延の処理を続行します。
  • 「プログラムの終了は最初のステートメントの後に記録されます。
  • 2ms の遅延の後、delayedFunction のステートメントが端末に記録されます。

出力

出力は次のように表示されます。

遅延関数の処理のためにコードが停止していないことがわかります。先に進み、遅延後に関数コールバックが処理されます。

保留中のコールバック

イベント ループは、ポーリング フェーズでファイルの読み取り、ネットワーク アクティビティ、入出力タスクなどのイベントが発生しているかどうかをチェックします。 Node.js では、このポーリング フェーズでは一部のイベントのみが処理されることを知っておくことが重要です。ただし、その後のイベント ループの繰り返しでは、特定のイベントが保留フェーズに延期される場合があります。これは、複雑なイベント駆動型の操作を伴う Node.js コードを最適化およびトラブルシューティングするときに留意すべき重要な概念です。

コールバックの待機フェーズでは、イベント ループが延期されたイベントを保留中のコールバックのキューに追加し、実行することを理解することが重要です。このフェーズでは、特定のオペレーティング システムでの ECONNREFUSED エラー イベントなど、システムが生成した一部の TCP ソケット エラーも処理します。

概念を明確にするために、以下に例を示します。

// アプリケーション.js
定数 fs = 必要とする ( 「fs」 ;
関数 readFileAsync ( ファイルパス、コールバック {
fs. ファイルの読み取り ( './PromiseText.txt' 'utf8' 、 関数 ( えー、データ {
もし ( エラー {
コンソール。 エラー ( ` エラー ファイルを読み取っています : $ { うーん。 メッセージ } ` ;
} それ以外 {
コンソール。 ログ ( ` ファイル コンテンツ : $ { データ } ` ;
}
折り返し電話 ( ;
} ;
}
コンソール。 ログ ( 「プログラムの始まり」 ;
readFileAsync ( './PromiseText.txt' 、 関数 ( {
コンソール。 ログ ( 「ファイル読み取りコールバックが実行されました」 ;
} ;
コンソール。 ログ ( 「番組終了」 ;

このコードでは:

  • プログラムは、端末に「プログラムの開始」というステートメントを記録することによって開始されます。
  • readFileAsync は、ファイル「PromiseText.txt」の内容を非同期的に読み取るために定義されています。これは、ファイルが読み取られた後にコールバック関数を実行するパラメータ化された関数です。
  • readFileAsync 関数は、ファイル読み取りプロセスを開始するために呼び出されます。
  • ファイルの読み取りプロセス中、プログラムは停止しません。代わりに、次のステートメントに進み、ターミナル「プログラムの終わり」にログインします。
  • ファイル読み込みの非同期イベントはイベントループによりバックグラウンドで処理されます。
  • ファイルが非同期で読み取られ、内容が端末に記録された後、プログラムはファイルの内容を端末に記録します。その後、「ファイル読み取りコールバックが実行されました」というメッセージがログに記録されます。
  • イベント ループは、次のフェーズで保留中のコールバック操作を処理します。

出力

上記の実行結果は次のようになります。

Node.js のアイドル、準備フェーズ

アイドル フェーズは Node.js の内部関数を処理するために使用されるため、標準フェーズではありません。コード スクリプトには影響しません。アイドル フェーズは、バックグラウンドで優先度の低いタスクを管理するイベント ループの休憩期間のようなものです。このフェーズを理解するための簡単な例は次のとおりです。

定数 { アイドル状態 } = 必要とする ( 「アイドルGC」 ;

アイドル。 無視する ( ;

このコードでは、アイドル フェーズを無視できる「idle-gc」モジュールが使用されています。これは、イベント ループがビジーでバックグラウンド タスクが実行されない状況に対処するために役立ちます。 idle.ignore の使用は、パフォーマンスの問題を引き起こす可能性があるため、最適とは考えられていません。

Node.js のポーリングフェーズ

Node.js のポーリング フェーズは次のように機能します。

  • ポーリング キュー内のイベントを処理し、対応するタスクを実行します。
  • プロセス内の I/O 操作の待機とチェックに費やす時間を決定します。

タイマーが存在しないためにイベント ループがポーリング フェーズに入ると、以下のタスクのいずれかが実行されます。

  • Node.js のイベント ループのポーリング フェーズでは、保留中の I/O イベントがキューに入れられ、キューが空になるまで先入れ先出しの原則に従って順次手順で実行されます。コールバックの実行中、nextTick キューとマイクロタスク キューも動作します。これによりスムーズさが保証され、I/O 操作をより効率的かつ確実に処理できるようになります。
  • キューが空で、スクリプトが setImmediate() 関数によってスケジュールされていない場合、イベント ループは終了し、次のフェーズ (チェック) に進みます。一方、スクリプトのスケジューリングが setImmediate() 関数によって行われている場合、イベント ループにより、イベント ループによって実行されるキューにコールバックを追加できます。

これは、簡単なコード例で最もよく説明されます。

setTimeout ( ( => {

コンソール。 ログ ( 「非同期操作が完了しました」 ;

} 2000年 ;

コンソール。 ログ ( '始める' ;

セット即時 ( ( => {

コンソール。 ログ ( 'setImmediate コールバックが実行されました' ;

} ;

コンソール。 ログ ( '終わり' ;

このコードでは:

  • 「Start」と「End」の 2 つのメッセージは、プログラムの開始と終了を示します。
  • setTimeout() 関数は、2 ミリ秒の遅延を持つコールバック関数を設定し、「非同期操作が完了しました」というログを端末に記録します。
  • setImmediate() 関数は、Start メッセージが端末に記録された後、「setImmediate コールバックが実行されました」メッセージを端末に記録します。

出力

出力には、「非同期操作が完了しました」に時間がかかり、「終了」メッセージの後に出力されることがわずか 1 分間観察されたメッセージが表示されます。

Node.js チェックフェーズ

ポーリングフェーズが実行された後、チェックフェーズのコールバックが実行されます。コード スクリプトが setImmediate() 関数を使用してスケジュールされており、ポーリング関数が空いている場合、イベント ループはアイドル状態に留まるのではなく、直接チェック フェーズに移行して機能します。 setImmediate() 関数は、イベント ループのさまざまなフェーズ中に動作する独自のタイマーです。

libuv API は、ポーリング フェーズの実行が完了した後にコールバックの実行を計画するために使用されます。コードの実行中、イベント ループはポーリング フェーズに入り、受信接続リクエストを待機します。別のケースでは、コールバックが setImmediate() 関数を使用してスケジュールされ、ポーリング フェーズがアクティビティなしで終了した場合、待機せずにチェック フェーズに移行します。理解するために次の例を考えてみましょう。

// アプリケーション.js

コンソール。 ログ ( '始める' ;

セット即時 ( ( => {

コンソール。 ログ ( 「即時コールバック」 ;

} ;

コンソール。 ログ ( '終わり' ;

このコードでは、3 つのメッセージが端末にログオンされます。次に、setImmediate() 関数は最後にメッセージをログに記録するコールバックを送信します。 即時コールバック 」を端末に伝えます。

出力

上記のコードの出力は次の順序で表示されます。

Node.jsのクローズコールバック

Node.js は、この終了フェーズを使用してコールバックを実行し、イベントを閉じ、イベント ループの反復を終了します。接続が閉じられた後、イベント ループはこのフェーズで終了イベントを処理します。イベント ループのこのフェーズでは、「nextTick()」とマイクロタスクが生成され、他のフェーズと同様に処理されます。

process.exit 関数は、イベント ループをいつでも終了するために使用されます。イベント ループは保留中の非同期操作を無視し、Node.js プロセスは終了します。

考慮すべき簡単な例は次のとおりです。

// アプリケーション.js
定数 ネット = 必要とする ( 'ネット' ;
定数 サーバ = ネット。 サーバーの作成 ( ( ソケット => {
ソケット。 の上 ( '近い' ( => {
コンソール。 ログ ( 「ソケットが閉じています」 ) ;
} ) ;
ソケット。 の上 ( 'データ' ( データ ) => {
コンソール。 ログ ( 「受信データ:」 、 データ。 toString ( ) ) ;
} ) ;
} ) ;
サーバ。 の上 ( '近い' ( ) => {
コンソール。 ログ ( 「サーバーが閉じられました」 ) ;
} ) ;
定数 ポート = 3000 ;
サーバ。 聞く ( ポート、 ( ) => {
コンソール。 ログ ( `ポート $ でリッスンしているサーバー { ポート } ` ) ;
} ) ;
setTimeout ( ( ) => {
コンソール。 ログ ( 「10秒後にサーバーを閉じます」 ) ;
サーバ。 近い ( ) ;
プロセス。 出口 ( ) ;
} 10000 ) ;

このコードでは:

  • const net = require('ネット') 「TCPサーバーを処理するために必要なネットモジュールをインポートします。」 const サーバー = net.createServer((ソケット) => { 」は、新しい TCP サーバー インスタンスを作成します。
  • ソケット.on('閉じる', () => {… } 」は、すべてのソケットの「close」をリッスンします。ソケット接続が閉じられると、「ソケットが閉じられました」というメッセージが端末に記録されます。
  • ソケット.on('データ', (データ) => {} 」は、すべての個々のソケットからの受信データをチェックし、「.toString()」関数を使用してそれを出力します。
  • server.on('close', () => {…} 」は、サーバー自体の「close」イベントをチェックし、サーバー接続が閉じられると、「Server Closed」メッセージを端末に記録します。
  • server.listen(ポート, () => {…} 」はポート上の着信接続をリッスンします。
  • setTimeout(() => {…} 」は、サーバーを閉じるタイマーを 10 ミリ秒に設定します。

これで、Node.js のイベント ループのさまざまなフェーズに関する説明は終わりです。結論に飛びつく前に、最後に Node.js のイベント ループを終了する方法について説明しましょう。

Node.js でのイベント ループの終了

イベント ループ フェーズのすべてのキューにいくつかのタスクがある限り、イベント ループは実行フェーズにあります。イベント ループは終了フェーズが発行された後に終了し、キュー内にタスクがなくなった場合は終了リスナー コールバックが戻ります。

イベント ループを明示的に終了する方法は、「.exit」メソッドを使用することです。 Node.js のアクティブなプロセスは、process.exit 関数が呼び出されるとすぐに終了します。スケジュールされたイベントと保留中のイベントはすべて削除されます。

プロセス。 の上 ( '出口' ( コード ) => {

コンソール。 ログ ( `終了コードで終了する : $ { コード } ` ) ;

} ) ;

プロセス。 出口 ( 1 ) ;

ユーザーは .exit 関数を聞くことができます。 Node.js プログラムはこのイベントをリッスンするとすぐに終了するため、「.exit」関数は同期する必要があることに注意してください。

これでイベント ループに関する説明は終わりです。イベント ループに関連するすべての概念、フェーズ、例を網羅した詳細な記事。

結論

イベント ループを理解する前に、同期および非同期の概念の概要を理解すると、イベント ループ内のコード フローを理解するのに役立ちます。同期実行はステップバイステップの実行を意味し、非同期実行は完了を待たずに一部のステップを停止することを意味します。この記事では、イベント ループの動作とすべてのフェーズ、および適切な例について説明します。