调用堆栈和事件循环 - 为什么要等待空堆栈?

Call Stack & Event loop - why waiting for empty stack?

我知道当调用堆栈为空时消息从队列进入调用堆栈。但是,如果事件循环可以将消息从队列直接推送到调用堆栈而无需等待,那不是更好吗?这种行为背后的原因是什么?如果事件循环会在准确的时间推送消息,我们总是可以依赖 setTimeout 等函数。

setTimeout(() => console.log("I want to be logged for 10ms, but I will never be :("), 10);

// some blocking operations
for(let i = 0; i < 500000000; i++){
  Math.random() * 2 + 2 - 3;  
}

console.log("I'll be logged first lol");

由于一致性原因,它可能永远不会更改,但我仍然很好奇。也许我没有看到什么,等待空堆栈的概念背后有严重的技术原因。您是否可以访问一些有关 JS 中的架构决策的文章,或者您是否知道在需要这种行为时的基本示例?有很多关于 JS 是如何工作的文章,但我找不到像 "Why event loop works exactly that way" 这样的文章。任何帮助将不胜感激。

这里是 V8 开发人员。这个问题似乎是基于对 "the call stack" 是什么的误解:它 不是 任何人都可以将其推入的数据结构。相反,它是一组函数相互调用时事物的当前状态的术语。 "push" 另一个函数进入调用堆栈的唯一方法是当前正在执行的函数调用它时。如果事件系统在随机位置插入随机调用到您的函数中,那将导致非常奇怪的编程模型。

您可以设计一个概念上相似的编程环境,但不是将任何内容压入调用堆栈,而是中断和挂起当前正在执行的任何内容,然后执行 setTimeout 计划函数 (或事件处理程序等),然后恢复之前的执行。您必须解决的一个问题是:如果这种情况重复发生怎么办,即如果预定函数被另一个预定函数打断,而另一个预定函数又被另一个预定函数打断怎么办,等等?如果预定函数需要永远完成怎么办:先前执行的代码何时才能再次取得进展?此外,虽然这可以在单线程世界中完成,但获得随机中断是并发(从一致性的角度来看相当于 parallelism/multi-threading),因此您需要像锁这样的同步原语(本质上,有函数说 "don't interrupt this section" 的一种方式——这反过来意味着您实际上无法保证调度请求的准确性)。不要低估所有这一切会给程序员带来的复杂性成本:在编写代码时,他们必须记住任何事情都可能随时被中断,另一方面,一个函数可能想要处理的任何数据可能还没有准备好,因为生成它的另一个函数尚未完成 运行。

所以简而言之,JavaScript的事件循环系统之所以如此,是因为该语言避免并发,随机中断函数执行其他函数就是并发,即使在单线程系统上也是如此。