Javascript 为什么while循环会阻止事件循环?

Javascript 为什么while循环会阻止事件循环?,javascript,node.js,Javascript,Node.js,Node.js手册中给出了以下示例: var open = false; setTimeout(function() { open = true }, 1000) while (!open) { console.log('wait'); } console.log('open sesame'); 在解释while循环阻止执行的原因时,作者说: 节点将永远不会执行超时回调,因为事件循环是 在7号线开始的时候就被困在这里了,从来没有给过它机会 处理超时事件 然而,作者并没有解释为什么

Node.js手册中给出了以下示例:

var open = false;

setTimeout(function() {
  open = true
}, 1000)

while (!open) {
  console.log('wait');
}

console.log('open sesame');
在解释while循环阻止执行的原因时,作者说:

节点将永远不会执行超时回调,因为事件循环是 在7号线开始的时候就被困在这里了,从来没有给过它机会 处理超时事件

然而,作者并没有解释为什么会在事件循环的上下文中发生这种情况,也没有解释引擎盖下到底发生了什么

有人能详细说明一下吗?为什么节点会卡住?以及如何更改上述代码,同时保留
while
控制结构,以使事件循环不被阻塞,并且代码将按照合理预期的方式运行;等待 将仅记录1秒,然后触发
setTimeout
并在记录“open sesame”后退出进程


关于IO和事件循环以及回调的一般性解释(如答案)并不能真正帮助我理顺这一点。我希望直接引用上述代码的答案会有所帮助。

节点是一个单一的串行任务。没有并行性,它的并发性是IO绑定的。可以这样想:一切都在一个线程上运行,当您进行阻塞/同步的IO调用时,您的进程将停止,直到返回数据;但是,假设我们有一个线程,它不等待IO(读取磁盘、抓取url等),而是继续执行下一个任务,并在该任务完成后检查IO。这基本上就是节点所做的,它是一个“事件循环”,在循环上轮询IO以完成(或进度)。因此,当任务未完成(循环)时,事件循环不会进行。简单地说。

真的很简单。在内部,node.js由以下类型的循环组成:

  • 从事件队列中获取某些内容
  • 运行指示的任何任务,并运行它直到它返回
  • 完成上述任务后,从事件队列中获取下一项
  • 运行指示的任何任务,并运行它直到它返回
  • 冲洗、起泡、重复——一遍又一遍
如果在某个时刻,事件队列中没有任何内容,则进入睡眠状态,直到事件队列中有内容


因此,如果一段Javascript位于
while()
循环中,则该任务未完成,并且按照上述顺序,在之前的任务完成之前,不会从事件队列中提取任何新的内容。因此,一个长时间或永远运行的
while()
循环只会让工作变得一团糟。因为Javascript一次只运行一个任务(对于JS执行来说是单线程的),如果一个任务在while循环中旋转,那么其他任何任务都无法执行

这里有一个简单的例子可以帮助解释这一点:

 var done = false;

 // set a timer for 1 second from now to set done to true
 setTimeout(function() {
      done = true;
 }, 1000);

 // spin wait for the done value to change
 while (!done) { /* do nothing */}

 console.log("finally, the done value changed!");
有些人可能从逻辑上认为while循环将一直旋转,直到计时器触发,然后计时器将
done
的值更改为
true
,然后while循环将完成,最后的
console.log()
将执行。这不是将要发生的事情。这实际上是一个无限循环,
console.log()
语句将永远不会执行

问题是,一旦进入
while()
循环中的旋转等待,就无法执行其他Javascript。因此,想要更改
done
变量值的计时器无法执行。因此,while循环条件永远不会改变,因此它是一个无限循环

以下是JS引擎内部发生的情况:

  • done
    变量初始化为
    false
  • setTimeout()
    从现在开始安排计时器事件1秒
  • while循环开始旋转
  • while循环旋转1秒后,计时器在JS引擎内部触发,计时器回调被添加到事件队列中。这可能发生在JS引擎内部的另一个线程上
  • while循环继续旋转,因为
    done
    变量从未更改。因为它继续旋转,JS引擎永远不会完成这个执行线程,也永远不会从事件队列中提取下一项

  • node.js是一个事件驱动的环境。要在实际应用程序中解决此问题,将在将来的某个事件中更改
    done
    标志。因此,与旋转
    while
    循环不同,您将为将来的某个相关事件注册一个事件处理程序,并在那里完成您的工作。在绝对最坏的情况下,您可以设置一个循环计时器和“轮询”来经常检查标志,但在几乎每种情况下,您都可以为实际事件注册一个事件处理程序,该事件将导致
    done
    标志发生更改,并在该事件中执行您的工作。正确设计的代码知道其他代码想知道什么时候发生了更改,甚至可以提供自己的事件侦听器和通知事件,用户可以注册感兴趣的事件,甚至只需一个简单的回调。

    这是一个很好的问题,但我找到了一个解决方案

    var sleep = require('system-sleep')
    var done = false
    
    setTimeout(function() {
      done = true
    }, 1000)
    
    while (!done) {
      sleep(100)
      console.log('sleeping')
    }
    
    console.log('finally, the done value changed!')
    

    我认为这是有效的,因为系统睡眠不是一个旋转等待。

    因为计时器需要返回并等待循环完成以添加到队列中,所以尽管超时在单独的线程中,并且可能确实找到了计时器,但“任务”设置done=true就是等待无限循环完成,还有另一个解决方案。几乎每个周期都可以访问事件循环

    let done = false;
    
    setTimeout(() => {
      done = true
    }, 5);
    
    const eventLoopQueue = () => {
      return new Promise(resolve => 
        setImmediate(() => {
          console.log('event loop');
          resolve();
        })
      );
    }
    
    const run = async () => {
      while (!done) {
        console.log('loop');
        await eventLoopQueue();
      }
    }
    
    run().then(() => console.log('Done'));
    

    我不同意这是重复的。它引用了一段特定的代码和对这段代码的特定解释。它还询问如何重写代码。没问题:-)它只是说它可能是重复的,而不是重复的。@codecowboy在Node.js中只有一个主线程。所以所有的代码只能由该线程执行。
    setTimeout
    将计划在1秒后执行功能。所以它不会执行t