El bucle de eventos de JavaScript

El bucle de eventos es uno de los aspectos más importantes que hay que comprender sobre JavaScript. Esta publicación lo explica en términos simples.

Introducción

losBucle de eventoses uno de los aspectos más importantes para comprender sobre JavaScript.

He programado durante años con JavaScript, pero nunca hecompletamenteEntendido cómo funcionan las cosas bajo el capó. Está completamente bien no conocer este concepto en detalle, pero como de costumbre, es útil saber cómo funciona, y también puede que tengas un poco de curiosidad en este punto.

Esta publicación tiene como objetivo explicar los detalles internos de cómo funciona JavaScript con un solo hilo y cómo maneja las funciones asincrónicas.

Su código JavaScript se ejecuta en un solo subproceso. Solo está sucediendo una cosa a la vez.

Esta es una limitación que en realidad es muy útil, ya que simplifica mucho la forma en que programa sin preocuparse por problemas de concurrencia.

Solo necesita prestar atención a cómo escribe su código y evitar cualquier cosa que pueda bloquear el hilo, como llamadas de red síncronas o infinitasbucles.

En general, en la mayoría de los navegadores hay un bucle de eventos para cada pestaña del navegador, para aislar cada proceso y evitar una página web con bucles infinitos o un procesamiento pesado que bloquee todo el navegador.

El entorno gestiona múltiples bucles de eventos simultáneos, para manejar llamadas a API, por ejemplo.Trabajadores webtambién se ejecutan en su propio bucle de eventos.

Principalmente debes preocuparte de quetu codigose ejecutará en un solo bucle de eventos y escribirá código teniendo esto en cuenta para evitar bloquearlo.

Bloquear el bucle de eventos

Cualquier código JavaScript que tarde demasiado en devolver el control al bucle de eventos bloqueará la ejecución de cualquier código JavaScript en la página, incluso bloqueará el hilo de la interfaz de usuario y el usuario no podrá hacer clic, desplazarse por la página, etc.

Casi todas las primitivas de E / S en JavaScript no son bloqueantes. Solicitudes de red,Node.jsoperaciones del sistema de archivos, etc. El bloqueo es la excepción, y esta es la razón por la que JavaScript se basa tanto en devoluciones de llamada y, más recientemente, enpromesasyasync / await.

La pila de llamadas

La pila de llamadas es una cola LIFO (último en entrar, primero en salir).

El bucle de eventos comprueba continuamente elpila de llamadaspara ver si hay alguna función que deba ejecutarse.

Mientras lo hace, agrega cualquier llamada de función que encuentre a la pila de llamadas y ejecuta cada una en orden.

¿Conoce el seguimiento de la pila de errores con el que puede estar familiarizado, en el depurador o en la consola del navegador? El navegador busca los nombres de las funciones en la pila de llamadas para informarle qué función origina la llamada actual:

Exception call stack

Una explicación simple del ciclo de eventos

Escojamos un ejemplo:

yo suelofoo,barybazcomonombres aleatorios. Ingrese cualquier tipo de nombre para reemplazarlos

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

const baz = () => console.log(‘baz’)

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

foo()

Este código imprime

foo
bar
baz

as expected.

When this code runs, first foo() is called. Inside foo() we first call bar(), then we call 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.

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.

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().

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.

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.

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.

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.

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.


More js tutorials: