ホームページ  >  記事  >  ウェブフロントエンド  >  Node.js のコールバック キューの詳細な分析

Node.js のコールバック キューの詳細な分析

青灯夜游
青灯夜游転載
2020-09-01 10:39:061620ブラウズ

Node.js のコールバック キューの詳細な分析

キューは、非同期操作を効率的に処理するための Node.js の重要なテクノロジです。 [ビデオチュートリアルの推奨: node js チュートリアル]

この記事では、Node.js のキューについて詳しく説明します。キューとは何か、(イベント ループを介して) どのように機能するかについて説明します。 )とそのタイプ。

Node.js のキューとは何ですか?

キューは、Node.js で非同期操作を整理するために使用されるデータ構造です。これらの操作は、HTTP リクエスト、ファイルの読み取りまたは書き込み操作、ストリームなど、さまざまな形式で存在します。

Node.js での非同期操作の処理は非常に困難です。

ネットワークの品質によっては、HTTP リクエスト中に予測できない遅延 (または、最悪の場合、結果が得られない) が発生する可能性があります。ファイルのサイズによっては、Node.js を使用してファイルを読み書きしようとすると遅延が発生する場合もあります。

タイマーや他の多くの操作と同様、非同期操作の完了時間も不確実な場合があります。

これらのさまざまな遅延シナリオでは、Node.js はこれらすべての操作を効率的に処理できる必要があります。

Node.js は、first-start-first-handle (first-start-first-handle) または first-finish-first-handle (first-finish-first-handle) に基づく操作を処理できません。

これができない理由の 1 つは、非同期操作に別の非同期操作が含まれる可能性があることです。

最初の非同期プロセス用のスペースを確保するということは、キュー内の他の非同期操作が考慮される前に、内部の非同期プロセスが完了する必要があることを意味します。

考慮すべき状況は数多くあるため、最善の選択肢はルールを作成することです。このルールは、Node.js でのイベント ループとキューの動作に影響します。

Node.js が非同期操作をどのように処理するかを簡単に見てみましょう。

コール スタック、イベント ループ、およびコールバック キュー

コール スタックは、現在実行中の関数とその開始場所を追跡するために使用されます。関数が実行される直前に、その関数は呼び出しスタックに追加されます。これは、JavaScript が関数の実行後に処理ステップを再追跡するのに役立ちます。

コールバックキューは、バックグラウンド操作の完了時にコールバック関数を非同期操作として保存するキューです。これらは先入れ先出し (FIFO) 方式で機能します。この記事の後半では、さまざまな種類のコールバック キューについて説明します。

JavaScript はシングルスレッドの性質を利用して新しいスレッドが生成されるのを防ぐことができるため、Node.js がすべての非同期アクティビティを担当することに注意してください。

バックグラウンド操作の完了後、コールバック キューに関数を追加する役割も担います。 JavaScript 自体はコールバック キューとは何の関係もありません。同時に、イベント ループはコール スタックが空かどうかを継続的にチェックして、関数をコールバック キューから抽出してコール スタックに追加できるようにします。イベント ループは、すべての同期操作が実行された後にのみキューをチェックします。

それでは、イベント ループはどのような順序でキューからコールバック関数を選択するのでしょうか?

まず、コールバック キューの 5 つの主要なタイプを見てみましょう。

コールバック キューの種類

IO キュー (IO キュー)

IO 操作は、外部デバイス (コンピューターのハードディスク、ネットワーク カードなど) に関係する操作を指します。一般的な操作には、ファイルの読み取りおよび書き込み操作、ネットワーク操作などが含まれます。これらの操作は Node.js に処理を委ねるため、非同期である必要があります。

JavaScript はコンピューターの内部デバイスにアクセスできません。このような操作が実行されると、JavaScript はそれを Node.js に転送してバックグラウンドで処理します。

完了後、それらはイベント ループの IO コールバック キューに転送され、実行のためにコール スタックに転送されます。

タイマー キュー

Node.js タイマー関数 を含むすべての操作 (setTimeout()setInterval()## など) #) はすべてタイマー キューに追加されます。

JavaScript 言語自体にはタイマー機能がないことに注意してください。 Node.js によって提供されるタイマー API (

setTimeout を含む) を使用して、時間関連の操作を実行します。したがって、タイマー操作は非同期です。 2 秒であっても 0 秒であっても、JavaScript は時間関連の操作を Node.js に引き渡し、Node.js が操作を完了してタイマー キューに追加します。 例:

setTimeout(function() {
        console.log('setTimeout');
    }, 0)
    console.log('yeah')


# 返回
yeah
setTimeout

非同期操作を処理するとき、JavaScript は引き続き他の操作を実行します。すべての同期操作が処理された後でのみ、イベント ループはコールバック キューに入ります。

マイクロタスク キュー (マイクロタスク キュー)

キューは 2 つのキューに分割されています:

最初のキューには理由が含まれています

process.nextTick
    関数と遅延関数。
  • イベント ループによって実行される各反復は、ティック (タイム スケール) と呼ばれます。
  • process.nextTick 是一个函数,它在下一个 tick (即事件循环的下一个迭代)执行一个函数。微任务队列需要存储此类函数,以便可以在下一个 tick 执行它们。

    这意味着事件循环必须继续检查微任务队列中的此类函数,然后再进入其他队列。

    • 第二个队列包含因 promises 而延迟的函数。

    如你所见,在 IO 和计时器队列中,所有与异步操作有关的内容都被移交给了异步函数。

    但是 promise 不同。在 promise 中,初始变量存储在 JavaScript 内存中(你可能已经注意到了fcfd9c63d2b5ae1697cab5937aad0a2f)。

    异步操作完成后,Node.js 会将函数(附加到 Promise)放在微任务队列中。同时它用得到的结果来更新 JavaScript 内存中的变量,以使该函数不与 fcfd9c63d2b5ae1697cab5937aad0a2f 一起运行。

    以下代码说明了 promise 是如何工作的:

    let prom = new Promise(function (resolve, reject) {
            // 延迟执行
            setTimeout(function () {
                return resolve("hello");
            }, 2000);
        });
        console.log(prom);
        // Promise { <pending> }
        
        prom.then(function (response) {
            console.log(response);
        });
        // 在 2000ms 之后,输出
        // hello

    关于微任务队列,需要注意一个重要功能,事件循环在进入其他队列之前要反复检查并执行微任务队列中的函数。例如,当微任务队列完成时,或者说计时器操作执行了 Promise 操作,事件循环将会在继续进入计时器队列中的其他函数之前参与该 Promise 操作。

    因此,微任务队列比其他队列具有最高的优先级。

    检查队列(Check queue)

    检查队列也称为即时队列(immediate queue)。IO 队列中的所有回调函数均已执行完毕后,立即执行此队列中的回调函数。setImmediate 用于向该队列添加函数。

    例如:

    const fs = require(&#39;fs&#39;);
    setImmediate(function() {
        console.log(&#39;setImmediate&#39;);
    })
    // 假设此操作需要 1ms
    fs.readFile(&#39;path-to-file&#39;, function() {
        console.log(&#39;readFile&#39;)
    })
    // 假设此操作需要 3ms
    do...while...

    执行该程序时,Node.js 把 setImmediate 回调函数添加到检查队列。由于整个程序尚未准备完毕,因此事件循环不会检查任何队列。

    因为 readFile 操作是异步的,所以会移交给 Node.js,之后程序将会继续执行。

    do while  操作持续 3ms。在这段时间内,readFile 操作完成并被推送到 IO 队列。完成此操作后,事件循环将会开始检查队列。

    尽管首先填充了检查队列,但只有在 IO 队列为空之后才考虑使用它。所以在 setImmediate 之前,将 readFile 输出到控制台。

    关闭队列(Close queue)

    此队列存储与关闭事件操作关联的函数。

    包括以下内容:

    这些队列被认为是优先级最低的,因为此处的操作会在以后发生。

    你肯sing不希望在处理 promise 函数之前在 close 事件中执行回调函数。当服务器已经关闭时,promise 函数会做些什么呢?

    队列顺序

    微任务队列具有最高优先级,其次是计时器队列,I/O队列,检查队列,最后是关闭队列。

    回调队列的例子

    让我们通过一个更复杂的例子来说明队列的类型和顺序:

    const fs = require("fs");
    
    // 假设此操作需要 2ms
    fs.writeFile(&#39;./new-file.json&#39;, &#39;...&#39;, function() {
        console.log(&#39;writeFile&#39;)
    })
    
    // 假设这需要 10ms 才能完成 
    fs.readFile("./file.json", function(err, data) {
        console.log("readFile");
    });
    
    // 不需要假设,这实际上需要 1ms
    setTimeout(function() {
        console.log("setTimeout");
    }, 1000);
    
    // 假设此操作需要 3ms
    while(...) {
        ...
    }
    
    setImmediate(function() {
        console.log("setImmediate");
    });
    
    // 解决 promise 需要 4 ms
    let promise = new Promise(function (resolve, reject) {
        setTimeout(function () {
            return resolve("promise");
        }, 4000);
    });
    promise.then(function(response) {
        console.log(response)
    })
    
    console.log("last line");

    程序流程如下:

    • 在 0 毫秒时,程序开始。
    • 在 Node.js 将回调函数添加到 IO 队列之前,fs.writeFile 在后台花费 2 毫秒。

    fs.readFile takes 10ms at the background before Node.js adds the callback function to the IO queue.

    • 在 Node.js 将回调函数添加到 IO 队列之前,fs.readFile 在后台花费 10 毫秒。
    • 在 Node.js 将回调函数添加到计时器队列之前,setTimeout 在后台花费 1ms。
    • 现在,while 操作(同步)需要 3ms。在此期间,线程被阻止(请记住 JavaScript 是单线程的)。
    • 同样在这段时间内,setTimeoutfs.writeFile 操作完成,并将它们的回调函数分别添加到计时器和 IO 队列中。

    现在的队列是:

    // queues
    Timer = [
        function () {
            console.log("setTimeout");
        },
    ];
    IO = [
        function () {
            console.log("writeFile");
        },
    ];

    setImmediate 将回调函数添加到 Check 队列中:

    js
    // 队列
    Timer...
    IO...
    Check = [
        function() {console.log("setImmediate")}
    ]

    在将 promise 操作添加到微任务队列之前,需要花费 4ms 的时间在后台进行解析。

    最后一行是同步的,因此将会立即执行:

    # 返回
    "last line"

    因为所有同步活动都已完成,所以事件循环开始检查队列。由于微任务队列为空,因此它从计时器队列开始:

    // 队列
    Timer = [] // 现在是空的
    IO...
    Check...
    
    
    # 返回
    "last line"
    "setTimeout"

    当事件循环继续执行队列中的回调函数时,promise 操作完成并被添加到微任务队列中:

    // 队列
        Timer = [];
        Microtask = [
            function (response) {
                console.log(response);
            },
        ];
        IO = []; // 当前是空的
        Check = []; // 当前是在 IO 的后面,为空
    
    
        # results
        "last line"
        "setTimeout"
        "writeFile"
        "setImmediate"

    几秒钟后,readFile 操作完成,并添加到 IO 队列中:

    // 队列
        Timer = [];
        Microtask = []; // 当前是空的
        IO = [
            function () {
                console.log("readFile");
            },
        ];
        Check = [];
    
    
        # results
        "last line"
        "setTimeout"
        "writeFile"
        "setImmediate"
        "promise"

    最后,执行所有回调函数:

    // 队列
        Timer = []
        Microtask = []
        IO = [] // 现在又是空的
        Check = [];
    
    
        # results
        "last line"
        "setTimeout"
        "writeFile"
        "setImmediate"
        "promise"
        "readFile"

    这里要注意的三点:

    • 异步操作取决于添加到队列之前的延迟时间。并不取决于它们在程序中的存放顺序。
    • 事件循环在每次迭代之继续检查其他任务之前,会连续检查微任务队列。
    • 即使在后台有另一个 IO 操作(readFile),事件循环也会执行检查队列中的函数。这样做的原因是此时 IO 队列为空。请记住,在执行 IO 队列中的所有的函数之后,将会立即运行检查队列回调。

    总结

    JavaScript 是单线程的。每个异步函数都由依赖操作系统内部函数工作的 Node.js 去处理。

    Node.js 负责将回调函数(通过 JavaScript 附加到异步操作)添加到回调队列中。事件循环会确定将要在每次迭代中接下来要执行的回调函数。

    了解队列如何在 Node.js 中工作,使你对其有了更好的了解,因为队列是环境的核心功能之一。 Node.js 最受欢迎的定义是 non-blocking(非阻塞),这意味着异步操作可以被正确的处理。都是因为有了事件循环和回调队列才能使此功能生效。

    更多编程相关知识,可访问:编程教学!!

以上がNode.js のコールバック キューの詳細な分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はsegmentfault.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。