首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >Node.js事件循环到底是什么?

Node.js事件循环到底是什么?
EN

Stack Overflow用户
提问于 2013-11-06 20:57:23
回答 5查看 27K关注 0票数 107

我对Node.js体系结构的内部结构有了更深入的了解,我经常看到的一个术语是“蜱”,如“事件循环的下一个滴答”或函数nextTick()

我没有看到的是一个确切的定义“滴答”是什么。基于不同的文章(就像这个),我能够把一个概念拼凑在我的脑海中,但我不知道它有多准确。

我能得到Node.js事件循环滴答的精确而详细的描述吗?

EN

回答 5

Stack Overflow用户

回答已采纳

发布于 2013-11-06 21:52:02

请记住,虽然JavaScript是单线程的,但节点的所有I/O和对本机API的调用要么是异步的(使用特定于平台的机制),要么是在单独的线程上运行。(这一切都是通过libuv处理的。)

因此,当套接字上可用的数据或本地API函数返回时,我们需要一种同步的方法来调用对刚刚发生的特定事件感兴趣的JavaScript函数。

从发生本机事件的线程中调用JS函数是不安全的,其原因与在常规多线程应用程序竞争条件、非原子内存访问等中遇到的原因相同。

因此,我们所做的就是以线程安全的方式将事件放在队列中。在过于简化的psuedocode中,类似于:

代码语言:javascript
运行
复制
lock (queue) {
    queue.push(event);
}

然后,回到主JavaScript线程(但在C端),我们执行如下操作:

代码语言:javascript
运行
复制
while (true) {
    // this is the beginning of a tick

    lock (queue) {
        var tickEvents = copy(queue); // copy the current queue items into thread-local memory
        queue.empty(); // ..and empty out the shared queue
    }

    for (var i = 0; i < tickEvents.length; i++) {
        InvokeJSFunction(tickEvents[i]);
    }

    // this the end of the tick
}

while (true) (实际上并不存在于节点的源代码中;这纯粹是说明性的)表示事件循环。内部for为队列中的每个事件调用JS函数。

这是一个滴答:与任何外部事件关联的零或多个回调函数的同步调用。一旦队列被清空并返回最后一个函数,滴答就结束了。我们回到开头(下一个勾号),检查在运行JavaScript时从其他线程添加到队列中的事件。

什么东西可以添加到队列中?

  • process.nextTick
  • setTimeout/setInterval
  • I/O (来自fsnet等的东西)
  • crypto的处理器密集型函数,如密码流、pbkdf2和PRNG (它们实际上是.的一个例子)
  • 使用libuv工作队列进行同步C/C++库调用的任何本机模块看起来都是异步的
票数 180
EN

Stack Overflow用户

发布于 2018-02-18 01:33:26

对于那些刚加入JavaScript的人来说,一个简单的答案是:

首先要理解的是,JavaScript是一个“单线程环境”。这指的是JavaScript在单个线程上一次从“事件循环”执行代码块的行为。下面是从凯尔辛普森的书“ydkJS”和“事后”中摘录的事件循环的基本含义,并给出一个解释:

代码语言:javascript
运行
复制
// `eventLoop` is an array that acts as a queue (first-in, first-out)
var eventLoop = [ ];
var event;

// keep going "forever"
while (true) {
    // perform a "tick"
    if (eventLoop.length > 0) {
        // get the next event in the queue
        event = eventLoop.shift();

        // now, execute the next event
        try {
            event();
        }
        catch (err) {
            reportError(err);
        }
    }
}

第一个while循环模拟事件循环。滴答是指事件从“事件循环队列”中脱队列和执行所述事件.

请参阅“Josh3796”的响应,以更详细地解释事件的脱队列和执行过程中所发生的事情。

此外,我建议那些有兴趣深入了解JavaScript的人阅读凯尔辛普森的书。它是完全免费和开放源码的,可以在以下链接中找到:https://github.com/getify/You-Dont-Know-JS

我所引用的特定部分可以在这里找到:https://github.com/getify/You-Dont-Know-JS/blob/2nd-ed/sync-async/ch1.md

票数 13
EN

Stack Overflow用户

发布于 2022-06-15 14:23:02

代码语言:javascript
运行
复制
                          ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐             
                              THE EVENT LOOP                  
                          └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘             
                                                              
                     ┌───────────────────────────────┐        
                     │             poll              │        
                  ┌─▶│                               │──┐     
                  │  └───────────────┬───────────────┘  │     
                  │                  │                 tick   
                  │  ┌───────────────▼───────────────┐  │     
                  │  │             check             │  │     
                  │  │                               │◀─┘     
                  │  └───────────────┬───────────────┘        
                  │                  │                        
                  │  ┌───────────────▼───────────────┐        
                  │  │        close callbacks        │        
                  │  │                               │        
                loop └───────────────┬───────────────┘        
                  │                  │                        
                  │  ┌───────────────▼───────────────┐        
                  │  │            timers             │        
                  │  │                               │        
                  │  └───────────────┬───────────────┘        
                  │                  │                        
                  │  ┌───────────────▼───────────────┐        
                  │  │       pending callbacks       │        
                  │  │                               │        
                  │  └───────────────┬───────────────┘        
                  │                  │                        
                  │  ┌───────────────▼───────────────┐        
                  │  │         idle, prepare         │        
                  └──│                               │        
                     └───────────────────────────────┘        

事件循环(在Node.js中)是一个执行模型,在该模型中,脚本的各个方面按照定义的时间表以周期性的方式执行。

It事件循环由多个阶段组成(如上文所示)。每个阶段包含(1)一个调用堆栈,(2)一个回调队列。调用堆栈是执行代码的地方(在LIFO的基础上),而回调队列是代码被调度的位置(在FIFO的基础上),以便稍后放置在调用堆栈中以供执行。

这个回调队列可以细分为两个队列:一个microTask队列和一个macroTask队列。微任务(一旦调度)是在当前阶段中当前运行脚本之后立即执行的任务,而宏任务(一旦调度)则是在所述阶段的下一个循环中执行的任务(在该阶段中的任何微任务之后)。

事件循环在一个循环中反复运行,直到没有更多的工作要做。每个循环(贯穿所有阶段)都可以称为循环,而在给定队列中对脚本的每次完整调用都可以称为滴答。

这个滴答通常会从一个阶段发生到另一个阶段,但是当microTask队列和macroTask队列都不是空时(例如,在运行脚本中解析承诺时),它的then方法会将项添加到microTask队列中。

当您编写代码(例如在mycode.js文件中)并调用它(使用node mycode.js)时,将根据其编写方式使用事件循环执行此代码。

下面是一个示例脚本:

代码语言:javascript
运行
复制
process.nextTick(function() {
  console.log('next tick - 1 [scheduled from poll]');
});

console.log('poll phase - 1');

setImmediate(function() {
  console.log('check phase - 1');

  process.nextTick(function() {
    console.log('next tick - 2 [scheduled from check]');
  });

  Promise.resolve()
    .then(function() {
      console.log(`check phase - 1.1 [microTask]`);
    })
    .then(function() {
      console.log(`check phase - 1.2 [microTask]`);
    })
    .then(function() {
      setTimeout(function() {
        console.log('timers phase [scheduled from Promise in check]');
      });
      process.nextTick(function() {
        console.log('next tick - 3 [scheduled from Promise in check]');
      });
    });

  console.log('check phase - 2');
});

setTimeout(function() {
  console.log('timers phase - 1');

  setImmediate(function() {
    console.log('check phase [scheduled from timers]');
  });

  Promise.resolve()
    .then(function() {
      console.log('timers phase - 1.1 [microTask]');
    })
    .then(function() {
      console.log('timers phase - 1.2 [microTask]');
    })
    .then(function() {
      setTimeout(function() {
        console.log('timers phase [scheduled from Promise in timers]');
      });
    });
});

process.nextTick(function() {
  console.log('next tick - 4 [scheduled from poll]');
});

console.log('poll phase - 2');

将此文件复制(或键入)到.js文件中,并使用node调用它。

您应该得到以下输出:

代码语言:javascript
运行
复制
poll phase - 1
poll phase - 2
next tick - 1 [scheduled from poll]
next tick - 4 [scheduled from poll]
check phase - 1
check phase - 2
next tick - 2 [scheduled from check]
check phase - 1.1 [microTask]
check phase - 1.2 [microTask]
next tick - 3 [scheduled from Promise in check]
timers phase - 1
timers phase - 1.1 [microTask]
timers phase - 1.2 [microTask]
timers phase [scheduled from Promise in check]
check phase [scheduled from timers]
timers phase [scheduled from Promise in timers]

注意事项:使用Node.js版本16.15.0

在解释之前,以下是一些需要记住的规则:

  • setImmediate计划脚本在事件循环的下一个检查阶段运行(在macroTask队列中)
  • setTimeout计划脚本在事件循环的下一个定时器阶段运行(在macroTask队列中)
  • 如果所述队列不是空的,则Process.nextTick计划脚本在下一个刻度之前运行,即(1)在当前脚本运行之后,但在microTask队列运行之前,或者(2)在事件循环之前运行,如果microTask队列为空,则从一个阶段遍历到下一个阶段。
  • Promise.prototype.then将脚本安排在当前microTask队列中运行,即在当前脚本之后,但在计划到下一阶段的脚本之前。
  • microTask队列在macroTask队列之前运行

以下是以事件时间表的形式作出的解释:

A.来自投票阶段(循环1)

  1. console.log('poll phase - 1')console.log('poll phase - 2')是同步代码,将在当前阶段立即运行。
  2. console.log('next tick - 1 [scheduled from poll]')console.log('next tick - 4 [scheduled from poll]')process.nextTick计划在下一个滴答之前运行,即在检查阶段之前运行(因为microTask队列中没有任何内容)。
  3. setImmediate上的回调(第7行)计划在检查阶段运行
  4. setTimeout上的回调(第33行)计划在计时器阶段运行

在检查阶段(循环1)之前5.执行console.log('next tick - 1 [scheduled from poll]')console.log('next tick - 4 [scheduled from poll]')

从检查阶段(循环1) 6. console.log('check phase - 1')console.log('check phase - 2')setImmediate以前计划的回调(第7行)立即执行,因为它们是同步的7。console.log('next tick - 2 [scheduled from check]')process.nextTick 8调度。第15、18和21行上的回调计划在microTask队列中运行。9.执行console.log('next tick - 2 [scheduled from check]') (因为这是在下一个滴答之前,即当前脚本之后,但在microTask队列之前) 10。执行第15行和第18行的回调(因为在运行脚本之后立即执行microTask ) 11。执行第21行的回调,并安排(1)在下一个定时器阶段运行console.log('timers phase [scheduled from Promise in check]'),(2)在下一个滴答之前运行console.log('next tick - 3 [scheduled from Promise in check]'),即在从当前阶段(检查)遍历到下一个活动阶段(定时器)之前运行。

在定时器阶段(循环1)之前12.执行console.log('next tick - 3 [scheduled from Promise in check]')

从定时器阶段(循环1) 13.执行console.log('timers phase - 1') 14。setImmediate (第36行)安排在下一个检查阶段15中运行回调。Promise (第40行)调度在microTask队列16中运行三个回调。console.log('timers phase - 1.1 [microTask]')console.log('timers phase - 1.2 [microTask]')按计划在15.17中执行。console.log('timers phase [scheduled from Promise in check]')被执行。它以前是由setTimeout计划的(第22行)。它现在正在运行(在上面的代码之后),因为它是一个macroTask (所以它在运行microTask队列之后运行)

从下一个检查阶段(循环2)开始执行18. console.log('check phase [scheduled from timers]')。它以前被setImmediate (第36行)安排在计时器阶段(循环1)。

从下一次定时器阶段(循环2) 19. console.log('timers phase [scheduled from Promise in timers]')被执行。它以前被setTimeout (第48行)安排在计时器阶段(循环1)。

参考资料

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/19822668

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档