この記事は、実際の開発および学習における nodejs についての個人的な理解をまとめたものであり、参考になれば幸いです。
I/O : 入力/出力、システムの入出力。
システムは、人などの個人として理解できます。話すときは出力であり、聞くときは入力です。
ブロッキング I/O とノンブロッキング I/O の違いは、入力から出力までの期間にシステムが他の入力を受信できるかどうかにあります。
以下に、ブロッキング I/O とノンブロッキング I/O が何であるかを示す 2 つの例を示します。
1. クッキング
まず最初に、システムの範囲を決定する必要があります。この例では、食堂のおばさんとレストランのウェイターがシステムとして扱われ、出力は料理の提供と見なされます。
次に、注文してから料理を提供するまでの間に他の人の注文を受け入れることができるかどうか、それがブロック I/O であるかノンブロック I/O であるかを判断できます。
食堂のおばちゃんは、注文するときに他の生徒に代わって注文することができず、生徒が注文して料理を提供し終わってからでないと次の生徒の注文を受けられないので、食堂のおばちゃんはI/Oをブロックしています。
レストランのウェイターの場合、注文後、ゲストが料理を提供する前に次のゲストにサービスを提供できるため、ウェイターはノンブロッキング I/O を利用できます。
2. 家事をする
洗濯するときは、洗濯機を待つ必要はありません。この時点で、机の上を掃除した後、洗濯物を干して乾かすだけです。合計25分。
洗濯は実際にはノンブロッキング I/O であり、洗濯機に衣類を入れてから洗濯が完了するまでの間に他のことを行うことができます。
ノンブロッキング I/O によってパフォーマンスが向上する理由は、不必要な待機を節約できるためです。
ノンブロッキング I/O を理解する鍵は、
NodejsのノンブロッキングI/Oはどのように反映されるのでしょうか?前述したように、ノンブロッキング I/O を理解する上で重要な点は、最初にノードのシステム境界を決定することです。
以下のアーキテクチャ図をスレッド保守に従って分割すると、左側の点線がnodejsスレッド、右側の点線がC++スレッドになります。
ここで、nodejs スレッドはデータベースにクエリを実行する必要があります。これは一般的な I/O 操作であり、I/O の結果を待たずに他の操作の処理を続行します。計算用のスレッド。
結果が出るまで待って、それをnodejsスレッドに返します。結果を取得する前に、nodejsスレッドは他のI/O操作も実行できるため、ノンブロッキングです。
nodejs スレッドは左側の部分がウェイターに相当し、C++ スレッドがシェフに相当します。
したがって、ノードのノンブロッキング I/O は、C++ ワーカー スレッドを呼び出すことによって完了します。
では、C++ スレッドが結果を取得したときに、nodejs スレッドに通知するにはどうすればよいでしょうか?答えはイベント駆動型です。
ブロッキング: プロセスは I/O 中にスリープし、次のステップに進む前に I/O が完了するのを待ちます。
非ブロッキング: 関数は I/O 中にすぐに戻り、プロセスは I/O を待ちません。 O 完了します。
したがって、返された結果を知るには、イベントドライバーを使用する必要があります。
いわゆるイベントドリブンは、最初にクリックイベントを作成しますが、それがいつトリガーされるかはわかりません。トリガーされたときにのみメインスレッドが実行されます。イベント駆動型関数を実行します。
このモードはオブザーバー モードでもあります。つまり、最初にイベントをリッスンし、トリガーされたときにそれを実行します。
では、イベントドライブを実装するにはどうすればよいでしょうか?答えは非同期プログラミングです。
前述したように、nodejs には多数のノンブロッキング I/O があるため、ノンブロッキング I/O の結果はコールバック関数を通じて取得する必要があります。このコールバック関数を使用する方法が非同期プログラミングです。たとえば、次のコードはコールバック関数を通じて結果を取得します。
glob(__dirname+'/**/*', (err, res) => { 結果 = 解像度 console.log('結果の取得') })
nodejs コールバック関数の最初のパラメータは error で、後続のパラメータは result です。なぜこれを行うのでしょうか?
試す { インタビュー(関数() { console.log('笑顔') }) } キャッチ(エラー) { console.log('叫び'、エラー) } 関数インタビュー(コールバック) { setTimeout(() => { if(Math.random() < 0.1) { コールバック('成功') } それ以外 { 新しいエラーをスロー('失敗') } }、500)実行後
、
エラーは捕捉されず、エラーがグローバルにスローされ、nodejs プログラム全体がクラッシュしました。
setTimeout はイベント ループを再度開くため、setTimeout コールバック関数が実行されるとコール スタック コンテキストが再生成されます。呼び出しスタックのコンテキスト すべてが異なります。この新しい呼び出しスタックには try catch がないため、エラーはグローバルにスローされ、キャッチできません。詳細については、「非同期キューで try catch を実行するときの問題」を参照してください。
それで、どうすればいいでしょうか?エラーをパラメータとして渡します:
function Interview(callback) { setTimeout(() => { if(Math.random() < 0.5) { コールバック('成功') } それ以外 { コールバック(新しいエラー('失敗')) } }、500) } インタビュー(関数 (解像度) { if (エラーのインスタンス) { console.log('叫び') 戻る } console.log('笑顔') })
しかし、これはさらに面倒で、コールバックで判断する必要があるため、最初のパラメータが存在しない場合は実行が成功したことを意味します。
関数インタビュー(コールバック) { setTimeout(() => { if(Math.random() < 0.5) { コールバック(null, '成功') } それ以外 { コールバック(新しいエラー('失敗')) } }、500) } インタビュー(関数 (解像度) { if (res) { 戻る } console.log('笑顔') })
nodejsのコールバック記述方式は、コールバック領域の発生だけでなく、非同期処理制御の問題も引き起こします。
非同期プロセス制御とは、主に、同時実行が発生したときに同時実行ロジックをどのように処理するかを指します。引き続き上記の例を使用します。同僚が 2 社の面接を受ける場合、2 社の面接に成功するまで 3 社目の面接は受けられません。では、このロジックはどのように記述すればよいでしょうか。変数 count をグローバルに追加する必要があります:
var count = 0 インタビュー((エラー) => { if (エラー) { 戻る } カウント++ if (カウント >= 2) { // 処理ロジック} }) インタビュー((エラー) => { if (エラー) { 戻る } カウント++ if (カウント >= 2) { // 処理ロジック} })
上記のような書き方は非常に面倒で見苦しいです。そのため、promiseやasync/awaitという書き方が後から登場しました。
現在のイベント ループでは結果を取得できませんが、将来のイベント ループでは結果が得られることが
悪党の言うこととよく似ています。
Promise は単なるクソ野郎なだけではなく、ステートマシンでもあります。
const pro = new Promise((resolve,拒否) => { setTimeout(() => { 解決('2') }、200) }) console.log(pro) // Print: Promise { <pending> }
then または catch を実行すると、新しい Promise が返されます。Promise の最終状態は、Then と catch のコールバック関数の実行結果によって決まります。
関数インタビュー() { return new Promise((解決、拒否) => { setTimeout(() => { if (Math.random() > 0.5) { 解決('成功') } それ以外 { 拒否(新しいエラー('失敗')) } }) }) } varpromise = インタビュー() varpromise1 =promise.then(() => { return new Promise((解決、拒否) => { setTimeout(() => { 解決('受け入れ') }、400) }) })
Promise1 の状態は、Return の Promise の状態、つまり、Return の Promise が実行された後の Promise1 の状態によって決まります。これにはどのような利点があるのでしょうか?これにより、コールバック地獄の問題が解決されます。
varpromise = インタビュー() .then(() => { インタビューに戻る() }) .then(() => { インタビューに戻る() }) .then(() => { インタビューに戻る() }) .catch(e => { コンソール.ログ(e) })
次に、返された Promise のステータスが拒否された場合、最初の catch が呼び出され、後続の then は呼び出されません。覚えておいてください: 拒否された呼び出しは最初のキャッチで、解決された呼び出しは最初のキャッチです。
Promise が地獄のコールバックを解決するだけである場合、Promise の主な機能は非同期プロセス制御の問題を解決することです。 2 つの企業に同時に面接したい場合:
function Interview() { return new Promise((解決、拒否) => { setTimeout(() => { if (Math.random() > 0.5) { 解決('成功') } それ以外 { 拒否(新しいエラー('失敗')) } }) }) } 約束 .all([インタビュー(), インタビュー()]) .then(() => { console.log('笑顔') }) // 会社が拒否した場合は、それをキャッチします .catch(() => { console.log('叫び') })
sync/await とは正確には何ですか:
console.log(async function() { 4を返す }) console.log(function() { return new Promise((解決、拒否) => { 解決(4) }) })
出力される結果は同じです。つまり、async/await は単なる Promise の糖衣構文です。
try catch はコール スタックに基づいてエラーをキャプチャし、コール スタックより上のエラーのみをキャプチャできることがわかっています。ただし、await を使用すると、コール スタック内のすべての関数のエラーをキャッチできます。 setTimeout などの別のイベント ループの呼び出しスタックでエラーがスローされた場合でも。
インタビュー コードを変換すると、コードが大幅に合理化されたことがわかります。
試す { 面接を待つ(1) 面接を待つ(2) 面接を待つ(2) } catch(e => { コンソール.ログ(e) })
並列タスクの場合はどうなるでしょうか?
await Promise.all([interview(1), Interview(2)])
Nodejs のノンブロッキング I/0 のため、イベントを達成するには、I/O 結果を取得するためにイベント駆動型メソッドを使用する必要があります。 -driven メソッドで結果を取得するには、コールバック関数などの非同期プログラミングを使用する必要があります。では、結果を得るためにこれらのコールバック関数を実行するにはどうすればよいでしょうか?次に、イベントループを使用する必要があります。
イベント ループは、nodejs のノンブロッキング I/O 機能を実現するための重要な基盤です。ノンブロッキング I/O およびイベント ループは、C++ ライブラリlibuv
によって提供される機能です。
コードのデモ:
consteventloop = { 列: []、 ループ() { while(this.queue.length) { const コールバック = this.queue.shift() 折り返し電話() } setTimeout(this.loop.bind(this), 50) }、 add(コールバック) { this.queue.push(コールバック) } } イベントループ.ループ() setTimeout(() => { イベントループ.add(() => { console.log('1') }) }、500) setTimeout(() => { イベントループ.add(() => { console.log('2') })setTimeout(this.loop.bind(this), 50)
は
setTimeout(this.loop.bind(this), 50)
50 ミリ秒後にキューにコールバックがあるかどうかを確認し、あればそれを実行します。これによりイベント ループが形成されます。
もちろん、実際のイベントはさらに複雑で、たとえば、ファイル操作キューや時間キューなど、複数のキューがあります。
const イベントループ = { 列: []、 fsQueue: []、 タイマーキュー: []、 ループ() { while(this.queue.length) { const コールバック = this.queue.shift() 折り返し電話() } this.fsQueue.forEach(callback => { if (完了) { 折り返し電話() } }) setTimeout(this.loop.bind(this), 50) }、 add(コールバック) { this.queue.push(コールバック) } まとめ
まず、ノンブロッキング I/O とは何か、つまり、I/O が発生したときに後続のタスクの実行を直ちにスキップし、I/O の結果を待たないことを理解します
I/Oが処理されると、登録したイベント処理関数が呼び出されます。これをイベントドリブンと呼びます。イベント ドライブを実装するには、非同期プログラミングが必要です。非同期プログラミングは、コールバック関数から Promise、そして最後に (同期メソッドを使用して非同期ロジックを作成する) に至る最も重要なリンクです。