为什么从无限 while 循环内部调用时不调用 setTimeout

why does the setTimeout not get called when invoked from inside an infinite while loop

我遇到了一个有趣的问题,我试图从一个无限循环中调用一个函数(它的主体中有一个 setTimeout),但它从来没有被调用过,但是当循环变成非无限循环时正在通话中!!

这个有效:-

var bar = function() {
    setTimeout(function() {
        console.log("1");
    }, 0);
};

var count = 4;
while(count > 0){
  bar();
  count --;
}

这永远行不通。 : -

var bar = function() {
  setTimeout(function() {
    console.log("1");
  }, 1000);
};

while (true) {
  bar();
}

谁能解释一下这里发生了什么! 如何从无限循环中调用函数体内带有 setTimeout 的函数?

这是因为事件循环的工作方式。像超时这样的异步事件被排队并在你的脚本主体runs-to-completion之后被处理。这意味着它正在等待 while 循环完成,然后它甚至开始查看 setTimeout 回调。这是您不想使用长 运行 同步代码(如 javascript 中的巨型循环)阻塞线程的(几个)原因之一。当您的无限 while 循环正在旋转时,不会发生任何其他事情。

我们先看看这段(无限)代码是如何执行的

CURRENT EXECUTION                               WAITING FOR EXECUTION (IN QUEUE)
===================                             ================================

=> variable bar is assigned to a function
=> while (true)
=> call bar()
=> inside bar()
=> setTimeout function will be sent to queue    => Execute setTimeout’s function after 1000ms(1)
=> while (true)                         
=> call bar()                           
=> inside bar()                         
=> setTimeout function will be sent to queue    => Execute setTimeout’s function after 1000ms(2)
=> call bar()                           
=> inside bar()                         
=> setTimeout function will be sent to queue    => Execute setTimeout’s function after 1000ms(3)
.
.
. while (true) => happens forever           => waits forever…

如果你想在无限循环中调用一个带有 setTimeout 的函数,那么你可以使用这样的东西,

var bar = function() {
    setTimeout(function() {
        console.log("1");
        runBar();
   }, 1000);
};

function runBar() {
    bar();
}
runBar();

这是因为您的 JavaScript 代码是单线程的。无限 while 循环使浏览器保持忙碌,没有时间执行其他异步函数,包括 bar 函数。

这就是 javascript 中事件循环的工作方式。 setTimeout() 回调被添加到宏任务队列中。只有当没有其他 JavaScript 正在执行中,或者调用堆栈为空时,宏任务队列才会被处理。因此,setTimeout() 回调无法执行,直到 while 循环(及其包含的代码)完成并且 returns 控制权返回给浏览器。

所以在示例 1 中:

var bar = function() {
    setTimeout(function() {
        console.log("1");
    }, 0);
};

var count = 4;
while(count > 0){
  bar();
  count --;
}

while 执行完成,returns 控制权返回给浏览器(callstack 为空),所以所有setTimeout() 回调被一个一个执行。

而在示例 2 中:

var bar = function() {
  setTimeout(function() {
    console.log("1");
  }, 1000);
};

while (true) {
  bar();
}

这是一个无限循环,所以控制权永远不会返回给浏览器或调用堆栈永远不会为空,因此事件循环无法处理宏任务队列,因此永远不会执行 setTimeout() 回调。

how to call a function with a setTimeout in its body from an infinite loop?

像那样:

let done = false;
let count = 0;

const bar = () => {
  setTimeout(() => {
    console.log(`bar ${count}`);
    count++;
  }, 5); // delay#1
}

const eventLoopQueue = () => {
  return new Promise(resolve => 
    setImmediate(() => {
      console.log('event loop');
      setTimeout(() => {
        if (count > 10) {
          done = true;
        }
        resolve();
      }, 5) //delay#2 should be equal or greater than delay#1
    })
  );
}

const run = async () => {
  while (!done) {
    bar();
    await eventLoopQueue();
  }
}

run().then(() => console.log('Done'));

输出:

event loop
bar 0
event loop
bar 1
event loop
bar 2
event loop
bar 3
event loop
bar 4
event loop
bar 5
event loop
bar 6
event loop
bar 7
event loop
bar 8
event loop
bar 9
event loop
bar 10
Done

当您需要在有条件退出的长循环或无限循环中执行某些操作并访问事件(鼠标点击、任何 socket.io 事件等)时,这种方法非常有用。

let done = false;

const eventEmitter = setInterval(() => {
    console.log('Hello! Are you here? Hello!');
  }, 0)

// This setTimeout just ends infinite loop in some time
// For demonstation purpose only
setTimeout(() => {
  done = true;
  clearInterval(eventEmitter);
}, 20);

// This gives us access to event loop queue
// If the events wait in the queue they will be released
const eventLoopQueue = () => {
  return new Promise(resolve => 
    setImmediate(() => {
      console.log('Next please! How can I help you?');
      resolve();
    })
  );
}

// run while loop 
const run = async () => {
  while (!done) {
    console.log('I am busy! Doing some work: part #1'); 
    await eventLoopQueue();
    console.log('I am busy! Doing some work: part #1');
    await eventLoopQueue();
    console.log('I am busy! Doing some work: part #1');
    await eventLoopQueue();
  }
}

run().then(() => console.log('Done'));