Visualización de promesas y asíncrono / espera



¡Buen dia amigos!

Les presento la traducción del artículo "JavaScript visualizado: promesas y asíncrono / espera" de Lydia Hallie.

¿Te has encontrado con un código JavaScript que ... no funciona como se esperaba? Cuando las funciones se realizan en un orden arbitrario e impredecible, o se realizan con retraso. Una de las principales tareas de las promesas es racionalizar la ejecución de funciones.

Mi curiosidad insaciable y mis noches de insomnio dieron sus frutos por completo, gracias a ellas creé varias animaciones. Es hora de hablar sobre las promesas: cómo funcionan, por qué deberían usarse y cómo se hace.

Introducción


Al escribir código JS, a menudo tenemos que lidiar con tareas que dependen de otras tareas. Supongamos que queremos obtener una imagen, comprimirla, aplicarle un filtro y guardarla.

En primer lugar, necesitamos obtener una imagen. Para hacer esto, use la función getImage. Después de cargar la imagen, la pasamos a la función resizeImage. Después de comprimir la imagen, le aplicamos un filtro usando la función applyFilter. Después de comprimir y aplicar el filtro, guardamos la imagen e informamos al usuario sobre el éxito.

Como resultado, obtenemos lo siguiente:



Hmm ... ¿Has notado algo? A pesar de que todo funciona, no se ve de la mejor manera. Obtenemos muchas funciones de devolución de llamada anidadas que dependen de devoluciones de llamada anteriores. Esto se llama infierno de devolución de llamada y hace que sea muy difícil leer y mantener el código.

Afortunadamente, hoy tenemos promesas.



Sintaxis de promesa


Las promesas se introdujeron en ES6. En muchos manuales puede leer lo siguiente:

Una promesa (promesa) es un valor que se cumple o rechaza en el futuro.

Sí ... más o menos explicación. En un momento, me hizo considerar las promesas como algo extraño, vago, algún tipo de magia. ¿Qué son realmente?

Podemos crear una promesa con un constructor Promiseque tome una función de devolución de llamada como argumento. Genial, intentemos:



Espera, ¿qué viene aquí?

PromiseEs un objeto que contiene status ( [[PromiseStatus]]) y value ( [[PromiseValue]]). En el ejemplo anterior, el valor [[PromiseStatus]]es pendingy el valor de la promesa es undefined.

No se preocupe, usted no tiene que interactuar con este objeto, ni siquiera se puede acceder al [[PromiseStatus]]y propiedades [[PromiseValue]]. Sin embargo, estas propiedades son muy importantes cuando se trabaja con promesas.

PromiseStatus o el estado de una promesa puede tomar uno de tres valores:

  • fulfilled: resolved (). ,
  • rejected: rejected (). -
  • pending: , pending (, )

Suena genial, pero ¿cuándo una promesa obtiene los estados indicados? ¿Y por qué importa el estado?

En el ejemplo anterior, pasamos una Promisefunción de devolución de llamada simple al constructor () => {}. Esta función en realidad toma dos argumentos. El valor del primer argumento, generalmente llamado resolveo res, es el método invocado cuando se ejecuta la promesa. El valor del segundo argumento, generalmente llamado rejecto rej, es el método llamado cuando la promesa se rechaza cuando algo sale mal.



Veamos qué se muestra en la consola al llamar a los métodos resolvey reject:



¡Genial! Ahora sabemos cómo deshacernos del estado pendingy el significado undefined. El estado de la promesa cuando se llama al método resolvees fulfilled, cuando reject:rejected.

[[PromiseValue]]o el valor de la promesa es el valor que pasamos a los métodos resolveo rejectcomo argumento.

Dato curioso: Jake Archibald, después de leer este artículo, señaló un error en Chrome, que en su lugar fulfilledregresó resolved.



Ok, ahora sabemos cómo trabajar con el objeto Promise. ¿Pero para qué se usa?

En la introducción, di un ejemplo en el que obtenemos una imagen, la comprimimos, le aplicamos un filtro y la guardamos. Entonces todo terminó con el infierno de devolución de llamada.

Afortunadamente, promete ayuda para hacer frente a esto. Reescribimos el código para que cada función devuelva una promesa.

Si la imagen se ha cargado, realizamos una promesa. De lo contrario, si se produce un error, rechace la promesa:



Veamos qué sucede cuando este código se ejecuta en la terminal:



¡Genial! Promis regresa con datos analizados ("analizados"), como esperábamos.

Pero ... ¿qué sigue? No estamos interesados ​​en el tema de promis, estamos interesados ​​en sus datos. Existen 3 métodos integrados para obtener un valor Promesa:

  • .then(): llamado después de realizar una promesa
  • .catch(): llamado después del rechazo de la promesa
  • .finally(): siempre llamado, tanto después de la ejecución como después del rechazo de una promesa



El método .thentoma el valor pasado al método resolve:



El método .catchtoma el valor pasado al método reject:



Finalmente, obtuvimos el valor deseado. Podemos hacer cualquier cosa con este valor.

Cuando estamos seguros en el cumplimiento o rechazo de una promesa, usted puede escribir Promise.resolvebien Promise.rejectcon el valor apropiado.



Esta es la sintaxis que se utilizará en los siguientes ejemplos.



El resultado .thenes el valor de la promesa (es decir, este método también devuelve la promesa). Esto significa que podemos usar tanto .thencomo sea necesario: el resultado del anterior .thense pasa como argumento al siguiente .then.



En getImagepodemos usar varios .thenpara transferir la imagen procesada a la siguiente función.



Esta sintaxis se ve mucho mejor que la escalera de funciones de devolución de llamada anidadas.



Microtask y tareas (macro)


Bueno, ahora sabemos cómo crear promesas y cómo extraer valores de ellas. Agregue algún código a nuestro script y ejecútelo nuevamente:



Primero, se muestra en la consola Start!. Esto es normal, ya que tenemos la primera línea de código console.log('Start!'). El segundo valor que se muestra en la consola End!no es el valor de la promesa completada. El valor de la promesa se muestra al final. ¿Por qué sucedió?

Aquí vemos el poder de las promesas. Aunque JS es de un solo subproceso, podemos hacer que el código sea asíncrono Promise.

¿Dónde más podríamos observar un comportamiento asincrónico? Algunos métodos integrados en el navegador, como por ejemplo setTimeout, pueden simular la asincronía.

Derecha En el bucle de eventos (Event Loop), hay dos tipos de colas: la cola de tareas (macro) o simplemente tareas (cola de tareas (macro), cola de tareas) y la cola de microtasks o simplemente microtasks (microtask queue, microtasks).

¿Qué se aplica a cada uno de ellos? En resumen, entonces:

  • Tareas de macro: setTimeout, setInterval, setImmediate
  • Microtasks: process.nextTick, Promise callback, queueMicrotask

Lo vemos Promiseen la lista de microtasks. Cuando Promisese ejecuta y se llama al método then(), catch()o finally()la función de devolución de llamada con el método se agrega a la cola de microtask. Esto significa que la devolución de llamada con el método no se ejecuta inmediatamente, lo que hace que el código JS sea asíncrono.

Cuando es el método then(), catch()o finally()se ejecutó? Las tareas en el bucle de eventos tienen la siguiente prioridad:

  1. Primero, se ejecutan las funciones en la pila de llamadas. Los valores devueltos por estas funciones se eliminan de la pila.
  2. Después de liberar la pila, las microtasks se colocan y ejecutan una tras otra (las microtasks pueden devolver otras microtasks, creando un ciclo interminable de microtasks).
  3. Una vez que se liberan la pila y la cola de microtask, el bucle de eventos busca macros. Las tareas de macro se insertan en la pila, se ejecutan y se eliminan.



Considere un ejemplo:

  • Task1: Una función que se agrega a la pila de inmediato, por ejemplo, mediante una llamada en código.
  • Task2, Task3, Task4: Mikrozadachi ejemplo thenProMIS o tarea añaden través queueMicrotask.
  • Task5, Task6: tareas macro, por ejemplo, setTimeoutosetImmediate



Primero Task1devuelve un valor y se elimina de la pila. Luego, el motor verifica si hay microtasks en la cola correspondiente. Después de agregar y posteriormente eliminar microtasks de la pila, el motor verifica las tareas de macro, que también se agregan a la pila y se eliminan después de devolver los valores.

Palabras suficientes Escribamos el código.



En este código, tenemos una macro setTimeouttarea y una micro tarea .then. Ejecute el código y vea lo que se muestra en la consola.

Nota: en el ejemplo anterior, uso métodos como console.log, setTimeouty Promise.resolve. Todos estos métodos son internos, por lo que no aparecen en el seguimiento de la pila; no se sorprenda cuando no los encuentre en las herramientas de solución de problemas del navegador.

En la primera línea tenemosconsole.log. Se agrega a la pila y se muestra en la consola Start!. Después de eso, este método se elimina de la pila y el motor continúa analizando el código.



El motor alcanza setTimeout, que se agrega a la pila. Este método es un método de navegador incorporado: su función de devolución de llamada ( () => console.log('In timeout')) se agrega a la API web y está allí antes de que se active el temporizador. A pesar de que el contador del temporizador es 0, la devolución de llamada todavía se coloca primero en el WebAPI y luego en la cola de tareas de macro: setTimeoutesta es una tarea de macro.



A continuación, el motor alcanza el método Promise.resolve(). Este método se agrega a la pila y luego se ejecuta con un valor Promise. Su devolución de llamada thense coloca en la cola de microtask.



Finalmente, el motor alcanza el segundo método console.log(). Se empuja inmediatamente a la pila, se envía a la consolaEnd!, el método se elimina de la pila y el motor continúa.



El motor "ve" que la pila está vacía. Comprobando la cola de tareas. Está ubicado allí then. Se inserta en la pila, el valor de la promesa se muestra en la consola: en este caso, una cadena Promise!.



El motor ve que la pila está vacía. Él "mira" en la cola de microtasks. Ella también está vacía.

Es hora de verificar la cola de tareas macro: está ahí setTimeout. Se empuja a la pila y devuelve un método console.log(). Se emite una cadena a la consola 'In timeout!'. setTimeoutse elimina de la pila.



Hecho. Ahora todo cayó en su lugar, ¿verdad?



Asíncrono / aguardar


ES7 introdujo una nueva forma de trabajar con código asincrónico en JS. Usando las palabras clave asyncy awaitpodemos crear una función asincrónica que implícitamente devuelve una promesa. ¿Pero cómo hacemos esto?

Anteriormente, discutimos cómo crear explícitamente un objeto Promise: usando new Promise(() => {}), Promise.resolveo Promise.reject.

En cambio, podemos crear una función asincrónica que implícitamente devuelva el objeto especificado. Esto significa que ya no necesitamos crear manualmente Promise.



El hecho de que una función asincrónica implícitamente devuelva una promesa es, por supuesto, genial, pero el poder de esta función se manifiesta completamente cuando se usa una palabra clave await.awaithace que la función asincrónica espere a que se complete la promesa (su valor). Para obtener el valor de la promesa realizada, debemos asignar a la variable el valor esperado (esperado) de la promesa.

Resulta que podemos retrasar la ejecución de una función asincrónica? Genial, pero ... ¿qué significa eso?

Veamos qué sucede cuando ejecuta el siguiente código:







Al principio el motor ve console.log. Este método se inserta en la pila y se muestra en la consola Before function!.



Luego se llama a la función asincrónica myFunc(), se ejecuta su código. En la primera línea de este código, llamamos a la segunda console.logcon una línea 'In function!'. Este método se agrega a la pila, su valor se muestra en la consola y se elimina de la pila.



El código de función se ejecuta a continuación. En la segunda línea tenemos una palabra clave await.

Lo primero que sucede aquí es la ejecución del valor esperado: en este caso, la función one. Se empuja a la pila y devuelve una promesa. Una vez que se ha completado la promesa y la función ha onedevuelto el valor, el motor ve await.

Después de eso, la ejecución de la función asincrónica se retrasa. Se suspende la ejecución del cuerpo de la función, el código restante se ejecuta como un microtask.



Después de que se ha retrasado la ejecución de la función asincrónica, el motor vuelve a la ejecución del código en un contexto global.



Después de que todo el código se haya ejecutado en un contexto global, el bucle de eventos busca microtasks y lo detecta myFunc(). myFunc()empujado a la pila y ejecutado.

La variable resobtiene el valor de la promesa ejecutada devuelta por la función one. Llamamos console.logcon el valor de la variable res: una cadena One!en este caso. One!Se muestra en la consola.

Hecho. ¿Nota la diferencia entre la función asincrónica y el método thenpromis? Palabra claveawaitpospone la ejecución de una función asincrónica. Si lo usáramos then, entonces el cuerpo de Promise continuaría funcionando.



Resultó bastante detallado. No se preocupe si se siente inseguro cuando trabaja con promesas. Lleva algún tiempo acostumbrarse a ellos. Esto es común a todas las técnicas para trabajar con código asincrónico en JS.

Consulte también "Visualización del trabajo de los trabajadores de servicios" .

Gracias por tu tiempo. Espero que haya sido bien aprovechado.

All Articles