ノードの「イベントループ」は、大きな並行性と高スループットを処理する能力の中核です。これは最も魔法のような場所です。これによれば、node.jsは基本的に「シングルスレッド」として理解することができ、任意の操作をバックグラウンドで処理することもできます。この記事では、イベントループがどのように機能するかを示し、あなたもその魔法を感じることができます。
イベント駆動型プログラミング
イベントループを理解するには、最初にイベントドライブプログラミングを理解する必要があります。 1960年に登場しました。今日、イベント駆動型プログラミングはUIプログラミングで広く使用されています。 JavaScriptの主な用途の1つは、DOMと対話することです。そのため、イベントベースのAPIを使用することは自然です。
簡単に定義する:イベント駆動型プログラミングは、イベントまたは状態の変更を通じてアプリケーションプロセスを制御します。通常、イベント監視を通じて実装されます。イベントが検出されると(つまり、状態が変更されます)、対応するコールバック関数が呼び出されます。おなじみのように聞こえますか?実際、これはnode.jsイベントループの基本的な作業原則です。
クライアント側のJavaScript開発に精通している場合は、ユーザーインタラクションを渡すためにDOM要素と組み合わせるために使用されるElement.onclick()などの.on*()メソッドについて考えてください。この作業モードにより、複数のイベントを単一のインスタンスでトリガーできます。 node.jsは、サーバー側のソケットや「HTTP」モジュールなど、EventeMitter(イベントジェネレーター)を介してこのモードをトリガーします。 1つ以上の状態の変更が、単一のインスタンスからトリガーされる場合があります。
別の一般的なパターンは、成功と失敗を表現することです。一般に、現在、2つの一般的な実装方法があります。最初のことは、「エラー例外」をコールバックに渡すことです。コールバックは、通常、最初のパラメーターとしてコールバック関数に渡されます。 2番目のタイプは、Promises Design Modeを使用することであり、ES6が追加されました。注* Promise Modeは、jQuery-like Functionチェーンライティング方法を使用して、次のようなディープコールバック機能のネストを避けます。
コードコピーは次のとおりです。
$ .getjson( '/getuser')。done(successhandler).fail(failhandler)
「FS」(Files -System)モジュールは、主に例外のスタイルをコールバックに使用します。 Fs.ReadFile()添付イベントなどの特定の呼び出しを技術的にトリガーしますが、APIは、操作の成功または失敗を表現するようにユーザーに思い出させるためにのみ使用されます。このようなAPIは、技術的な制限ではなく、建築上の理由で選択されます。
一般的な誤解は、イベントのエミッターもイベントをトリガーするときに本質的に非同期であるということですが、これは間違っています。これを証明するための簡単なコードスニペットです。
コードコピーは次のとおりです。
function myemitter(){
eventemitter.call(this);
}
util.inherits(myemitter、eventemitter);
myemitter.prototype.dostuff = function dostuff(){
console.log( 'before')
emitter.emit( 'fire')
console.log( 'after')}
};
var me = new myemitter();
me.on( 'fire'、function(){
console.log( 'emit fired');
});
me.dostuff();
//出力:
// 前に
//発射されたエミット
// 後
注* emitter.emitが非同期である場合、出力は必要です
// 前に
// 後
//発射されたエミット
EventeMitterは、非同期に完了する必要がある操作を通知するためによく使用されるため、非同期ではないことがよくありますが、EventeMitter API自体は完全に同期しています。リスニング機能は非同期に実行できますが、すべてのリスニング機能は、追加された順序で同期的に実行されることに注意してください。
メカニズムの概要とスレッドプーリング
ノード自体は複数のライブラリに依存しています。そのうちの1つはLibuvです。これは、非同期イベントのキューと実行を魔法のように処理するライブラリです。
ノードは、できるだけ多くの既存の関数を使用して、オペレーティングシステムカーネルを利用します。たとえば、応答要求が生成され、接続が転送され、処理のためにシステムに委任されます。たとえば、着信接続は、ノードで処理できるまでオペレーティングシステムを介してキューに登録されます。
ノードにはスレッドプールがあると聞いたことがあるかもしれません。「ノードがタスクを順番に処理する場合、なぜスレッドプールが必要なのですか?」これは、カーネルで、すべてのタスクが非同期に実行されるわけではないためです。この場合、node.jsは、操作中にスレッドを一定期間ロックして、ブロックされずにイベントループを実行し続けることができる必要があります。
以下は、内部動作メカニズムを示す簡単な図図です。
┌。—七面
──‑►TIMESS│
││。-└。-七面
│┌。-七面
CALLBACKSを保留中の│
│└。-└。-七面
│┌。-│。-七面
│世論調査│◄。なり職、つながり、
│└。。-七面
││。-│。-七面
‑─¶┤Setimmediate│
└└。。。。。です
イベントループの内部操作メカニズムを理解することには、いくつかの困難があります。
すべてのコールバックは、イベントループの1つのフェーズ(タイマーなど)の終了前にprocess.nextTick()を介してプリセットされ、次のフェーズに移行します。これにより、Process.nextTick()への再帰的な呼び出しの可能性が回避され、無限のループが発生します。
「保留中のコールバック」は、コールバックキューのコールバックであり、他のイベントループサイクルによって処理されません(たとえば、fs.writeに渡されます)。
イベントエミッタとイベントループ
EventeMitterを作成することにより、イベントループとの相互作用を簡素化できます。これは、イベントベースのAPIを簡単に作成できるようにする普遍的なカプセル化です。 2つの相互作用により、開発者が混乱していると感じることがよくあります。
次の例は、イベントが同期してトリガーされていることを忘れると、イベントが見逃される可能性があることを示しています。
コードコピーは次のとおりです。
// v0.10の後、( 'events')を要求します。EventeMitterは不要になりました
var eventemitter = require( 'events');
var util = require( 'util');
function mything(){
eventemitter.call(this);
dofirstthing();
this.emit( 'thing1');
}
util.inherits(mything、eventemitter);
var mt = new Mything();
mt.on( 'thing1'、function onthing1(){
//申し訳ありませんが、この事件は決して起こりません
});
Mything()はイベントを聞く前にインスタンス化する必要があるため、上記の「Thing1」イベントはMything()に巻き込まれません。追加の閉鎖を追加することなく、簡単な回避策を次に示します。
コードコピーは次のとおりです。
var eventemitter = require( 'events');
var util = require( 'util');
function mything(){
eventemitter.call(this);
dofirstthing();
setimmediate(emitthing1、this);
}
util.inherits(mything、eventemitter);
関数emitthing1(self){
self.emit( 'thing1');
}
var mt = new Mything();
mt.on( 'thing1'、function onthing1(){
// 実行する
});
次のスキームも機能しますが、いくつかのパフォーマンスは失われます。
コードコピーは次のとおりです。
function mything(){
eventemitter.call(this);
dofirstthing();
// function#bind()を使用すると、パフォーマンスが失われます
setimmediate(this.emit.bind(this、 'thing1'));
}
util.inherits(mything、eventemitter);
別の問題は、エラー(例外)のトリガーです。アプリケーションで問題を見つけるのはすでに困難ですが、コールスタック(注 *e.Stack)がなければ、デバッグはほとんど不可能です。リモートが非同期にエラーが要求されると、コールスタックが失われます。 2つの可能な解決策があります。同期トリガーのトリガーまたは他の重要な情報でエラーが渡されることを保証します。次の例は、これら2つのソリューションを示しています。
コードコピーは次のとおりです。
mything.prototype.foo = function foo(){
//このエラーは非同期にトリガーされます
var er = dofirstthing();
if(er){
//トリガーするときは、オンサイトのコールスタック情報を保持する新しいエラーを作成する必要があります。
setimmediate(emiterror、this、new error( 'bad stuff'));
戻る;
}
//エラーをトリガーしてすぐに処理します(同期)
var er = dosecondthing();
if(er){
this.emit( 'error'、 'More Bad Stuff');
戻る;
}
}
状況を評価します。エラーがトリガーされると、すぐに処理される可能性があります。あるいは、それは些細なものであり、簡単に処理することも、後で処理することもできます。さらに、構築されたオブジェクトインスタンスが不完全である可能性が高いため、コンストラクターにエラーを渡すことは良い考えではありません。例外は、今すぐエラーが直接スローされた場合です。
結論
この記事では、イベントループの内部動作メカニズムと技術的な詳細について簡単に説明します。すべてが慎重に検討されています。別の記事では、イベントループとシステムカーネル間の相互作用について説明し、非同期に実行されるNodejsの魔法を示します。