JavaScript事件循环

介绍 (Introduction)

The Event Loop is one of the most important aspects to understand about JavaScript.

事件循环是了解JavaScript的最重要方面之一。

I’ve programmed for years with JavaScript, yet I’ve never fully understood how things work under the hoods. It’s completely fine to not know this concept in detail, but as usual, it’s helpful to know how it works, and also you might just be a little curious at this point.

我已经使用JavaScript编程了多年,但我从未完全了解事物的幕后工作方式。 完全不了解此概念是完全可以的,但是像往常一样,了解它的工作原理会有所帮助,并且此时您可能会有点好奇。

This post aims to explain the inner details of how JavaScript works with a single thread, and how it handles asynchronous functions.

这篇文章旨在解释JavaScript如何与单个线程一起工作以及如何处理异步函数的内部细节。

Your JavaScript code runs single threaded. There is just one thing happening at a time.

您JavaScript代码运行单线程。 一次只发生一件事。

This is a limitation that’s actually very helpful, as it simplifies a lot how you program without worrying about concurrency issues.

这个限制实际上非常有用,因为它极大地简化了您的编程方式,而无需担心并发问题。

You just need to pay attention to how you write your code and avoid anything that could block the thread, like synchronous network calls or infinite loops.

您只需要注意如何编写代码,并避免任何可能阻塞线程的事情,例如同步网络调用或无限循环

In general, in most browsers there is an event loop for every browser tab, to make every process isolated and avoid a web page with infinite loops or heavy processing to block your entire browser.

通常,在大多数浏览器中,每个浏览器选项卡都有一个事件循环,以使每个进程都隔离开,并避免使用无限循环或繁重的处理来阻止整个浏览器的网页。

The environment manages multiple concurrent event loops, to handle API calls for example. Web Workers run in their own event loop as well.

该环境管理多个并发事件循环,以处理例如API调用。 Web Worker也运行在自己的事件循环中。

You mainly need to be concerned that your code will run on a single event loop, and write code with this thing in mind to avoid blocking it.

您主要需要担心代码将在单个事件循环上运行,并且在编写代码时要牢记这一点以避免阻塞它。

阻止事件循环 (Blocking the event loop)

Any JavaScript code that takes too long to return back control to the event loop will block the execution of any JavaScript code in the page, even block the UI thread, and the user cannot click around, scroll the page, and so on.

任何花费太长时间才能将控制权返回到事件循环JavaScript代码,都将阻止页面中任何JavaScript代码的执行,甚至阻止UI线程,并且用户无法单击浏览,滚动页面等。

Almost all the I/O primitives in JavaScript are non-blocking. Network requests, Node.js filesystem operations, and so on. Being blocking is the exception, and this is why JavaScript is based so much on callbacks, and more recently on promises and async/await.

JavaScript中几乎所有的I / O原语都是非阻塞的。 网络请求, Node.js文件系统操作等。 被阻止是一个例外,这就是JavaScript如此之多基于回调的原因,而最近基于Promiseasync / await

调用栈 (The call stack)

The call stack is a LIFO queue (Last In, First Out).

调用堆栈是一个LIFO队列(后进先出)。

The event loop continuously checks the call stack to see if there’s any function that needs to run.

事件循环不断检查调用堆栈,以查看是否需要运行任何函数。

While doing so, it adds any function call it finds to the call stack and executes each one in order.

这样做时,它将找到的所有函数调用添加到调用堆栈中,并按顺序执行每个函数。

You know the error stack trace you might be familiar with, in the debugger or in the browser console? The browser looks up the function names in the call stack to inform you which function originates the current call:

您知道在调试器或浏览器控制台中可能熟悉的错误堆栈跟踪吗? 浏览器在调用堆栈中查找函数名称,以通知您哪个函数发起了当前调用:

Exception call stack

一个简单的事件循环说明 (A simple event loop explanation)

Let’s pick an example:

让我们举个例子:

I use foo, bar and baz as random names. Enter any kind of name to replace them

我使用foobarbaz作为随机名称 。 输入任何名称以替换它们

const bar = () => console.log('bar')

const baz = () => console.log('baz')

const foo = () => {
  console.log('foo')
  bar()
  baz()
}

foo()

This code prints

此代码打印

foo
bar
baz

as expected.

如预期的那样。

When this code runs, first foo() is called. Inside foo() we first call bar(), then we call baz().

运行此代码时,将首先调用foo() 。 在foo()内部,我们首先调用bar() ,然后调用baz()

At this point the call stack looks like this:

此时,调用堆栈如下所示:

Call stack first example

The event loop on every iteration looks if there’s something in the call stack, and executes it:

每次迭代中的事件循环都会查看调用堆栈中是否有东西,并执行它:

Execution order first example

until the call stack is empty.

直到调用堆栈为空。

排队功能执行 (Queuing function execution)

The above example looks normal, there’s nothing special about it: JavaScript finds things to execute, runs them in order.

上面的示例看起来很正常,没有什么特别的:JavaScript查找要执行的事物,并按顺序运行它们。

Let’s see how to defer a function until the stack is clear.

让我们看看如何将函数推迟到堆栈清除之前。

The use case of setTimeout(() => {}), 0) is to call a function, but execute it once every other function in the code has executed.

setTimeout(() => {}), 0)是调用一个函数,但是一旦代码中的每个其他函数执行完毕,就执行一次。

Take this example:

举个例子:

const bar = () => console.log('bar')

const baz = () => console.log('baz')

const foo = () => {
  console.log('foo')
  setTimeout(bar, 0)
  baz()
}

foo()

This code prints, maybe surprisingly:

这段代码打印出来,也许令人惊讶:

foo
baz
bar

When this code runs, first foo() is called. Inside foo() we first call setTimeout, passing bar as an argument, and we instruct it to run immediately as fast as it can, passing 0 as the timer. Then we call baz().

运行此代码时,将首先调用foo()。 在foo()内部,我们首先调用setTimeout,将bar作为参数传递,并指示它尽快运行,并传递0作为计时器。 然后我们调用baz()。

At this point the call stack looks like this:

此时,调用堆栈如下所示:

Call stack second example

Here is the execution order for all the functions in our program:

这是程序中所有功能的执行顺序:

Execution order second example

Why is this happening?

为什么会这样呢?

消息队列 (The Message Queue)

When setTimeout() is called, the Browser or Node.js start the timer. Once the timer expires, in this case immediately as we put 0 as the timeout, the callback function is put in the Message Queue.

调用setTimeout()时,Browser或Node.js启动timer 。 一旦计时器到期,在这种情况下,我们立即将超时值设为0,则将回调函数放入Message Queue中

The Message Queue is also where user-initiated events like click or keyboard events, or fetch responses are queued before your code has the opportunity to react to them. Or also DOM events like onLoad.

在消息队列中,用户启动的事件(例如单击或键盘事件或获取响应)也将在此排队,然后您的代码才有机会对其进行响应。 或是DOM事件,例如onLoad

The loop gives priority to the call stack, and it first processes everything it finds in the call stack, and once there’s nothing in there, it goes to pick up things in the message queue.

循环将优先级赋予调用堆栈,它首先处理在调用堆栈中找到的所有内容,一旦其中没有任何内容,便开始处理消息队列中的内容。

We don’t have to wait for functions like setTimeout, fetch or other things to do their own work, because they are provided by the browser, and they live on their own threads. For example, if you set the setTimeout timeout to 2 seconds, you don’t have to wait 2 seconds - the wait happens elsewhere.

我们不必等待诸如setTimeout ,fetch或其他功能的功能来完成自己的工作,因为它们是由浏览器提供的,并且它们位于自己的线程中。 例如,如果将setTimeout超时设置为2秒,则不必等待2秒-等待发生在其他地方。

ES6作业队列 (ES6 Job Queue)

ECMAScript 2015 introduced the concept of the Job Queue, which is used by Promises (also introduced in ES6/ES2015). It’s a way to execute the result of an async function as soon as possible, rather than being put at the end of the call stack.

ECMAScript 2015引入了作业队列的概念,Promises使用了该队列(也在ES6 / ES2015中引入)。 这是一种尽快执行异步函数结果的方法,而不是放在调用堆栈的末尾。

Promises that resolve before the current function ends will be executed right after the current function.

在当前功能结束之前解决的承诺将在当前功能之后立即执行。

I find nice the analogy of a rollercoaster ride at an amusement park: the message queue puts you at the back of the queue, behind all the other people, where you will have to wait for your turn, while the job queue is the fastpass ticket that lets you take another ride right after you finished the previous one.

我发现在游乐园中过山车的比喻很好:消息队列将您排在队列的后面,在所有其他人的后面,您将不得不等待转弯,而工作队列是快速通道票这样您就可以在完成上一个骑行之后立即骑另一个骑行。

Example:

例:

const bar = () => console.log('bar')

const baz = () => console.log('baz')

const foo = () => {
  console.log('foo')
  setTimeout(bar, 0)
  new Promise((resolve, reject) =>
    resolve('should be right after baz, before bar')
  ).then(resolve => console.log(resolve))
  baz()
}

foo()

This prints

此打印

foo
baz
should be right after baz, before bar
bar

That’s a big difference between Promises (and Async/await, which is built on promises) and plain old asynchronous functions through setTimeout() or other platform APIs.

这是Promises(和基于Promise构建的Async / await)与通过setTimeout()或其他平台API的普通旧异步函数之间的巨大区别。

翻译自: https://flaviocopes.com/javascript-event-loop/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值