Incluso los niños entenderán: una explicación simple de async / wait y promesas en JavaScript

Hola Habr! Te presento la traducción del artículo "JavaScript asíncrono / espera y promesas: explicado como si tuvieras cinco años" por Jack Pordi.

Cualquiera que se considere un desarrollador de JavaScript, en algún momento, debería haber encontrado funciones de devolución de llamada, promesas o, más recientemente, la sintaxis async / wait. Si ha estado en el juego el tiempo suficiente, probablemente haya encontrado momentos en que las funciones de devolución de llamada anidadas eran la única forma de lograr la asincronía en JavaScript.

Cuando comencé a aprender y escribir en JavaScript, ya había mil millones de tutoriales y tutoriales que explicaban cómo lograr la asincronía en JavaScript. Sin embargo, muchos de ellos simplemente explicaron cómo convertir las funciones de devolución de llamada en promesas o promesas en asíncrono / espera. Para muchos, esto es probablemente más que suficiente para llevarse bien con ellos y comenzar a usarlos en su código.

Sin embargo, si usted, como yo, realmente quiere comprender la programación asincrónica (¡y no solo la sintaxis de JavaScript!), Entonces puede estar de acuerdo conmigo en que hay una escasez de materiales que expliquen la programación asincrónica desde cero.

¿Qué significa asíncrono?


la imagen muestra a una persona pensante

Como regla general, al hacer esta pregunta, puede escuchar algo de lo siguiente:

  • Hay varios subprocesos que ejecutan código al mismo tiempo.
  • Se ejecuta más de una pieza de código a la vez.
  • Esto es concurrencia.

Hasta cierto punto, todas las opciones son correctas. Pero en lugar de darle una definición técnica que probablemente olvidará pronto, daré un ejemplo que incluso un niño puede entender .

Ejemplo de vida


la foto muestra verduras y un cuchillo de cocina

Imagina que estás cocinando sopa de verduras. Para una analogía buena y simple, supongamos que una sopa de verduras consiste solo en cebollas y zanahorias. La receta para tal sopa puede ser la siguiente:

  1. Picar las zanahorias.
  2. Picar la cebolla.
  3. Agregue agua a la sartén, encienda la estufa y espere hasta que hierva.
  4. Agregue las zanahorias a la sartén y deje por 5 minutos.
  5. Agregue las cebollas a la sartén y cocine por otros 10 minutos.

Estas instrucciones son simples y comprensibles, pero si uno de ustedes, al leer esto, realmente sabe cocinar, puede decir que esta no es la forma más efectiva de cocinar. Y tendrá razón, por eso:

  • Los pasos 3, 4 y 5 en realidad no requieren que usted como chef haga nada excepto observar el proceso y llevar un registro del tiempo.
  • Los pasos 1 y 2 requieren que hagas algo activamente.

Por lo tanto, la receta para un cocinero más experimentado puede ser la siguiente:

  1. Comienza a hervir una olla de agua.
  2. Mientras espera que la olla hierva, comience a cortar zanahorias.
  3. Cuando termines de picar las zanahorias, el agua debe hervir, así que agrégalas.
  4. Mientras las zanahorias se cocinan en una sartén, pica las cebollas.
  5. Agregue las cebollas y cocine otros 10 minutos.

A pesar de que todas las acciones se mantuvieron igual, tiene derecho a esperar que esta opción sea mucho más rápida y eficiente. Este es exactamente el principio de la programación asincrónica: nunca querrás sentarte, solo esperar algo, mientras puedes dedicar tu tiempo a otras cosas útiles.

Todos sabemos que en la programación, esperar que algo suceda con bastante frecuencia, ya sea que esté esperando una respuesta HTTP de un servidor o una acción de un usuario u otra cosa. Pero los ciclos de ejecución de su procesador son preciosos y siempre deben usarse activamente, haciendo algo y sin esperar: esto da como resultado una programación asincrónica .

Ahora vamos a JavaScript, ¿de acuerdo?


Entonces, siguiendo el mismo ejemplo de sopa de verduras, escribiré algunas funciones para representar los pasos de la receta descrita anteriormente.

Primero, escribamos funciones síncronas que representen tareas que no toman tiempo. Estas son las funciones antiguas de JavaScript, pero tenga en cuenta que he descrito las chopOnions chopCarrots y chopOnions como tareas que requieren trabajo activo (y tiempo), lo que les permite hacer algunos cálculos largos. El código completo está disponible al final del artículo [1].

 function chopCarrots() { /*   ... */ console.log(" !"); } function chopOnions() { /*   ... */ console.log(" !"); } function addOnions() { console.log("   !"); } function addCarrots() { console.log("   !"); } 

Antes de pasar a las funciones asincrónicas, primero explicaré rápidamente cómo el sistema de tipos de JavaScript maneja la asincronía: básicamente todos los resultados (incluidos los errores) de las operaciones asincrónicas deben incluirse en una (s) promesa (s) .

Para que una función devuelva una promesa, puede:

  • devolver explícitamente la promesa, es decir return new Promise(…) ;
  • devuelve una promesa implícitamente: agregue la async a la declaración de función, es decir async function foo() ;
  • usa ambas opciones .

Hay un excelente artículo [2], que habla sobre la diferencia entre funciones asincrónicas y funciones que devuelven una promesa. Por lo tanto, en mi artículo no me detendré en este tema, lo más importante para recordar: siempre debe usar la async asíncrona en funciones asincrónicas.

Entonces, nuestras funciones asincrónicas, que representan los pasos 3-5 de la preparación de sopa de verduras, son las siguientes:

 async function letPotKeepBoiling(time) { return; //  ,      } async function boilPot() { return; //  ,       } 

Una vez más, eliminé los detalles de implementación para no distraerme con ellos, pero se publican al final del artículo [1].

Es importante saber que para esperar el resultado de la promesa, para que luego pueda hacer algo con ella, simplemente puede usar la palabra clave wait:

 async function asyncFunction() { /*  ... */ } result = await asyncFunction(); 

Entonces, ahora solo tenemos que poner todo junto:

 function makeSoup() { const pot = boilPot(); chopCarrots(); chopOnions(); await pot; addCarrots(); await letPotKeepBoiling(5); addOnions(); await letPotKeepBoiling(10); console.log("   !"); } makeSoup(); 

Pero espera! ¡Esto no funciona! Verá SyntaxError: await is only valid in async functions error de SyntaxError: await is only valid in async functions . Por qué Porque si no declara una función utilizando la async , entonces, de forma predeterminada, JavaScript la define como una función sincrónica, ¡y sincrónica significa que no hay que esperar! [3] También significa que no puede usar await fuera de una función.

Por lo tanto, simplemente agregamos la async a la función makeSoup :

 async function makeSoup() { const pot = boilPot(); chopCarrots(); chopOnions(); await pot; addCarrots(); await letPotKeepBoiling(5); addOnions(); await letPotKeepBoiling(10); console.log("   !"); } makeSoup(); 

Y voila! Tenga en cuenta que en la segunda línea, llamo a la función asincrónica boilPot sin la palabra clave wait, porque no queremos esperar a que hierva la sartén antes de comenzar a cortar las zanahorias. Esperamos solo la promesa de la pot en la quinta línea antes de que necesitemos poner las zanahorias en la sartén, porque no queremos hacer esto antes de que hierva el agua.

¿Qué sucede durante las llamadas en await ? Bueno, nada ... más o menos ...

En el contexto de la función makeSoup simplemente puede pensar que espera que suceda algo (o un resultado que finalmente se devolverá).

Pero recuerde: usted (como su procesador) nunca querrá sentarse allí y esperar algo, mientras puede dedicar su tiempo a otras cosas .

Por lo tanto, en lugar de cocinar sopa, podríamos cocinar otra cosa en paralelo:

 makeSoup(); makePasta(); 

Mientras esperamos letPotKeepBoiling , podemos, por ejemplo, cocinar pasta.

¿Ves? La sintaxis async / await es bastante fácil de usar, si lo entiendes, ¿estás de acuerdo?

¿Qué pasa con las promesas abiertas?


Bueno, si insiste, recurriré al uso de promesas explícitas ( comentario traducido : mediante promesas explícitas, el autor implica directamente la sintaxis de las promesas y, mediante promesas implícitas, la sintaxis async / wait, porque devuelve la promesa implícitamente; no es necesario escribir return new Promise(…) ). Tenga en cuenta que los métodos asíncronos / en espera se basan en las propias promesas y, por lo tanto, ambas opciones son totalmente compatibles .

Las promesas explícitas, en mi opinión, se encuentran entre las devoluciones de llamada de estilo antiguo y la nueva sintaxis sexual async / wait. Alternativamente, también puede pensar en la sintaxis sexual async / wait como nada más que promesas implícitas. Al final, la construcción asíncrona / espera llegó después de las promesas, que a su vez llegaron después de las funciones de devolución de llamada.

Usa nuestra máquina del tiempo para pasar al infierno de devolución de llamada [4]:

 function callbackHell() { boilPot( () => { addCarrots(); letPotKeepBoiling(() => { addOnions(); letPotKeepBoiling(() => { console.log("   !"); }, 1000); }, 5000); }, 5000, chopCarrots(), chopOnions() ); } 

No voy a mentir, escribí este ejemplo sobre la marcha cuando estaba trabajando en este artículo, y me llevó mucho más tiempo del que me gustaría admitir. Muchos de ustedes ni siquiera saben lo que está pasando aquí. Mi querido amigo, ¿no son horribles todas estas funciones de devolución de llamada? Que sea una lección para no volver a usar las funciones de devolución de llamada ...

Y, como se prometió, el mismo ejemplo con promesas explícitas:

 function makeSoup() { return Promise.all([ new Promise((reject, resolve) => { chopCarrots(); chopOnions(); resolve(); }), boilPot() ]) .then(() => { addCarrots(); return letPotKeepBoiling(5); }) .then(() => { addOnions(); return letPotKeepBoiling(10); }) .then(() => { console.log("   !"); }); } 

Como puede ver, las promesas siguen siendo similares a las funciones de devolución de llamada.
No entraré en detalles, pero lo más importante:

  • .then es un método prometedor que toma su resultado y lo pasa a la función de argumento (esencialmente, a una función de devolución de llamada ...)
  • Nunca puede usar el resultado de una promesa fuera del contexto de .then . En esencia, .then es como un bloque asincrónico que espera un resultado y luego lo pasa a una función de devolución de llamada.
  • Además del método .then , hay otro método en .catch : .catch . Es necesario para manejar errores en promesas. Pero no voy a entrar en detalles, porque ya hay mil millones de artículos y tutoriales sobre este tema.

Conclusión


Espero que tenga alguna idea de las promesas y la programación asincrónica de este artículo, o tal vez al menos haya aprendido un buen ejemplo de la vida para explicar esto a otra persona.

Entonces, ¿de qué manera usas: promesas o asíncrono / espera?
La respuesta es completamente suya, y diría que combinarlos no es tan malo, ya que ambos enfoques son completamente compatibles entre sí.

Sin embargo, personalmente estoy 100% en el campo asíncrono / espera, porque para mí el código es mucho más comprensible y refleja mejor la verdadera multitarea de la programación asincrónica.



[1] : el código fuente completo está disponible aquí .
[2] : Artículo original "Función asincrónica vs. una función que devuelve una Promesa " , traducción del artículo " La diferencia entre una función asincrónica y una función que devuelve una promesa " .
[3] : Puede argumentar que JavaScript probablemente puede determinar el tipo asíncrono / en espera por el cuerpo de la función y verificar de forma recursiva, pero JavaScript no fue diseñado para cuidar la seguridad del tipo estático en tiempo de compilación, sin mencionar que es mucho más conveniente para los desarrolladores ver explícitamente el tipo de función.
[4] : Escribí funciones "asincrónicas", asumiendo que funcionan bajo la misma interfaz que setTimeout . Tenga en cuenta que las devoluciones de llamada no son compatibles con las promesas y viceversa.

Source: https://habr.com/ru/post/474726/


All Articles