Javascript 脚本和setTimeout/setInterval如何在NodeJS中协同工作?

Javascript 脚本和setTimeout/setInterval如何在NodeJS中协同工作?,javascript,node.js,Javascript,Node.js,阅读全文,我想知道setTimeout和setInterval实际上是如何工作的 页面上说NodeJS首先运行给定的脚本,暂时不使用REPL,然后进入事件循环。但是,如果我在脚本中调用setTimeout,并期望它在脚本仍在运行时触发,该怎么办?这不是正常情况吗?根据描述,在主脚本结束之前不会触发计时器回调,这听起来真的很奇怪 对于那些感兴趣的人来说,这里是NodeJS外部偶数循环,实际上有两个嵌套循环:[EDIT]哼哼。在阅读您最初的帖子时,我想我知道您有什么问题。您在帖子中提到了nodejs

阅读全文,我想知道setTimeout和setInterval实际上是如何工作的

页面上说NodeJS首先运行给定的脚本,暂时不使用REPL,然后进入事件循环。但是,如果我在脚本中调用setTimeout,并期望它在脚本仍在运行时触发,该怎么办?这不是正常情况吗?根据描述,在主脚本结束之前不会触发计时器回调,这听起来真的很奇怪


对于那些感兴趣的人来说,这里是NodeJS外部偶数循环,实际上有两个嵌套循环:

[EDIT]哼哼。在阅读您最初的帖子时,我想我知道您有什么问题。您在帖子中提到了nodejs,这意味着您可能正在编写服务器代码

如果您对服务器端JavaScript不太熟悉,或者对php服务器不太熟悉,那么您可能会感到非常困惑

对于php服务器,请求将创建一个新线程来处理它,当您调用的主脚本结束时,该线程将被终止,除侦听请求的Web服务器(如nginx或apache)外,服务器上不会运行任何其他线程

有了nodejs,情况就不同了。主线程是单独的,并且始终在运行。因此,当一个请求到达时,回调被触发,但它们仍然在那个单线程中。否则:主脚本永远不会结束,除非您杀死它或服务器崩溃:

嗯,这是正确的。由于JavaScript的单线程特性,如果计时器在主线程繁忙时结束,计时器的回调将等待

当你这样做的时候

setTimeout(callback, 1000)
你不是说我希望这个回调在1s内被调用,但实际上我希望这个回调至少在1s内被调用


John Resig的这篇文章是一篇优秀的阅读材料,它详细介绍了JavaScript计时器的细节。您在帖子中提到了nodejs,这意味着您可能正在编写服务器代码

如果您对服务器端JavaScript不太熟悉,或者对php服务器不太熟悉,那么您可能会感到非常困惑

对于php服务器,请求将创建一个新线程来处理它,当您调用的主脚本结束时,该线程将被终止,除侦听请求的Web服务器(如nginx或apache)外,服务器上不会运行任何其他线程

有了nodejs,情况就不同了。主线程是单独的,并且始终在运行。因此,当一个请求到达时,回调被触发,但它们仍然在那个单线程中。否则:主脚本永远不会结束,除非您杀死它或服务器崩溃:

嗯,这是正确的。由于JavaScript的单线程特性,如果计时器在主线程繁忙时结束,计时器的回调将等待

当你这样做的时候

setTimeout(callback, 1000)
你不是说我希望这个回调在1s内被调用,但实际上我希望这个回调至少在1s内被调用


John Resig的这篇文章是一篇优秀的阅读材料,它详细介绍了JavaScript的计时器

如果节点是单线程的,那么它的v8引擎总是在进入下一个脚本之前执行当前脚本。因此,当我们用主脚本启动节点服务器时,它首先加载、解析、编译并执行该脚本,然后再运行其他任何操作。只有当当前正在运行的脚本命中I/O调用时,它才会跳转到事件循环的后面,给其他脚本或setTimeout回调一个执行的机会。这正是JavaScript引擎的本质,也是Node不适合长时间运行、内存中CPU密集型任务的原因


正如@atomrc在他的回答中所说,setTimeout和setInterval只是提示node在超时后运行回调,没有任何保证。

鉴于node是单线程的,它总是在进入下一个脚本之前执行当前脚本。因此,当我们用主脚本启动节点服务器时,它首先加载、解析、编译并执行该脚本,然后再运行其他任何操作。只有当当前正在运行的脚本命中I/O调用时,它才会跳转到事件循环的后面,给其他脚本或setTimeout回调一个执行的机会。这正是JavaScript引擎的本质,也是Node不适合长时间运行、内存中CPU密集型任务的原因

正如@atomrc在他的回答中所说的,setTimeout和setInterval只是提示节点在超时后运行回调,没有任何保证

但是,如果我在脚本中调用setTimeout,并期望它在脚本仍在运行时触发,该怎么办

你没料到。您希望同步代码在超时发生之前以完成方式运行

如果脚本仍然在运行,因为它正在执行阻塞操作-它挂起-那么超时回调没有机会执行,它将等待。那是 这正是我们需要编写非阻塞代码的原因

这不是正常情况吗

不会。大多数情况下,没有JS在执行,事件循环处于空闲状态,而可能有后台任务在执行繁重的任务

但是,如果我在脚本中调用setTimeout,并期望它在脚本仍在运行时触发,该怎么办

你没料到。您希望同步代码在超时发生之前以完成方式运行

如果脚本仍然在运行,因为它正在执行阻塞操作-它挂起-那么超时回调没有机会执行,它将等待。这正是我们需要编写非阻塞代码的原因

这不是正常情况吗

不会。大多数情况下,没有JS在执行,事件循环处于空闲状态,而可能有后台任务在执行繁重的任务。

让我们通过示例来实现这一点

这将在那里打印hi

发生了什么

该脚本将一直处理到最后一行,当它找到一个计时器函数时 它将把它添加到队列中,队列调度器稍后将在执行结束时处理该队列

循环队列=>[setTimeout]

在退出之前,应该有一个调度程序,某种循环来检查我们是否 在队列中有一些东西并处理它们,然后一旦队列超出所有计时器,循环就结束了 将退出

假设我们在setInterval内调用setTimeout

循环队列=>[setInterval]

1000毫秒后

将激发setInterval,并将内部setTimeout添加到队列中

循环队列=>[setTimeout,setInterval]

现在我们回到主回路,它将再等待500毫秒 触发内部setTimeout函数,然后将其从队列中删除 因为setTimeout应该运行一次

循环队列=>[setInterval]

回到主循环,队列中仍然有项目,因此它将等待 再发射500毫秒,再发射500+500=1000毫秒 内部setTimeout函数将再次添加到队列中

循环队列=>[setTimeout,setInterval]

再次回到主队列并再次

这就是定时器的工作原理,它们不是用来处理阻塞代码的,而是 以一定间隔运行代码的方法

setInterval(function(){
    // do something long running here
    while (1) {}
    setTimeout(function(){
        print('hi')
    }, 500);
}, 1000);
主循环将在此阻塞,内部超时不会添加到队列中,因此 这是个坏主意

nodej和事件循环通常很适合网络操作,因为它们在运行时不会阻塞 例如,与select一起使用

setInterval(function(){
    // check if socket has something
    if (socketHasData( socket )){
        processSocketData( socket );
    }

    // do something else that does not block
    // maybe schedule another timer here
    print('hello');
}, 1000);
libuv是nodejs中使用的事件循环,它使用线程来处理一些事件 阻止诸如IO操作、打开/读取/写入文件之类的操作

这将在那里打印hi

发生了什么

该脚本将一直处理到最后一行,当它找到一个计时器函数时 它将把它添加到队列中,队列调度器稍后将在执行结束时处理该队列

循环队列=>[setTimeout]

在退出之前,应该有一个调度程序,某种循环来检查我们是否 在队列中有一些东西并处理它们,然后一旦队列超出所有计时器,循环就结束了 将退出

假设我们在setInterval内调用setTimeout

循环队列=>[setInterval]

1000毫秒后

将激发setInterval,并将内部setTimeout添加到队列中

循环队列=>[setTimeout,setInterval]

现在我们回到主回路,它将再等待500毫秒 触发内部setTimeout函数,然后将其从队列中删除 因为setTimeout应该运行一次

循环队列=>[setInterval]

回到主循环,队列中仍然有项目,因此它将等待 再发射500毫秒,再发射500+500=1000毫秒 内部setTimeout函数将再次添加到队列中

循环队列=>[setTimeout,setInterval]

再次回到主队列并再次

这就是定时器的工作原理,它们不是用来处理阻塞代码的,而是 以一定间隔运行代码的方法

setInterval(function(){
    // do something long running here
    while (1) {}
    setTimeout(function(){
        print('hi')
    }, 500);
}, 1000);
主循环将在此阻塞,内部超时不会添加到队列中,因此 这是个坏主意

nodej和事件循环通常很适合网络操作,因为它们在运行时不会阻塞 例如,与select一起使用

setInterval(function(){
    // check if socket has something
    if (socketHasData( socket )){
        processSocketData( socket );
    }

    // do something else that does not block
    // maybe schedule another timer here
    print('hello');
}, 1000);
libuv是nodejs中使用的事件循环,它使用线程来处理一些事件
阻止IO操作、打开/读取/写入文件等操作

不幸的是,您做出了一些错误的假设:-我既没有实现服务器,也不习惯PHP。我想要了解的是,如果是单线程的,那么主脚本中的计时器回调集如何在NodeJS中的每个触发器上运行?我研究了NodeJS源代码一段时间,但它的工作原理一点也不明显。在我看来,在主脚本完成之前,似乎不会触发回调。Oups太糟糕了:/n我认为混淆之处在于您所称的主脚本。我不确定我是否明白你在说什么。注意:计时器在
浏览器是一个不同的东西,因为有几个线程涉及,而NodeJS不使用,从我看到的。不过,即使在浏览器中,这也只能在JS引擎支持在任意时间插入回调的情况下工作,或者在脚本运行时它们永远不会运行。顺便说一句,nodejs的源代码很容易阅读。我不确定我能做到这一点!!!关于主脚本:请参见链接的描述。它说NodeJS首先加载给定的脚本并运行它,然后启动事件循环。不幸的是,你做了一些错误的假设:-我既没有实现服务器,也不习惯PHP。我想要了解的是,如果是单线程的,那么主脚本中的计时器回调集如何在NodeJS中的每个触发器上运行?我研究了NodeJS源代码一段时间,但它的工作原理一点也不明显。在我看来,在主脚本完成之前,似乎不会触发回调。Oups太糟糕了:/n我认为混淆之处在于您所称的主脚本。我不确定我是否明白你们在说什么。注意:浏览器中的计时器是一个不同的东西,因为有几个线程涉及,而NodeJS并没有使用,从我所看到的。不过,即使在浏览器中,这也只能在JS引擎支持在任意时间插入回调的情况下工作,或者在脚本运行时它们永远不会运行。顺便说一句,nodejs的源代码很容易阅读。我不确定我能做到这一点!!!关于主脚本:请参见链接的描述。它说NodeJS首先加载给定的脚本并运行它,然后在该脚本结束后启动事件循环。当脚本仍在运行时,您到底理解什么?如前所述。NodeJS将处理初始脚本,然后转到事件循环。如果初始脚本从未返回(例如,有自己的循环,但希望使用setTimeout),该怎么办?是的,如果脚本阻塞-挂起-则超时回调将永远无法执行。这正是我们需要编写非阻塞代码的原因。当脚本仍在运行时,您到底理解了什么?如前所述。NodeJS将处理初始脚本,然后转到事件循环。如果初始脚本从未返回(例如,有自己的循环,但希望使用setTimeout),该怎么办?是的,如果脚本阻塞-挂起-则超时回调将永远无法执行。这正是我们需要编写非阻塞代码的原因。假设您运行一个脚本来测试某些东西。它需要一段时间来运行,并希望显示进度。为此,它使用计时器将进度节拍合并为一个不太小的间隔。如果您是对的,在测试脚本结束之前,永远不会显示进度输出。那么设置超时有什么好处呢?是的。如果测试代码同步运行,它还需要同步写入进度输出。您仍然可以使用Date来计算时间。当需要使用setTimeout时,is必须异步运行测试,即至少在其自己的进程中运行每个测试。它需要一段时间来运行,并希望显示进度。为此,它使用计时器将进度节拍合并为一个不太小的间隔。如果您是对的,在测试脚本结束之前,永远不会显示进度输出。那么设置超时有什么好处呢?是的。如果测试代码同步运行,它还需要同步写入进度输出。您仍然可以使用Date来计算时间。当需要使用setTimeout时,is必须异步运行测试,即至少在其自己的进程中运行每个测试。