JavaScript — ES8 (ECMA2017)-Parte III / async-await

Mauricio Garcia
7 min readOct 20, 2022

Temario

  • Introducción
  • Nuevas Características
    async
    await

i. Introducción

Esta story es la continuación de:

  • ES8 — ES2017 (I[ref], II [ref])
  • ES7 — ES2016[ref]
  • ES6 — ES2015 (I[ref], II[ref],III[ref],IV[ref],V[ref])
  • JavaScript — Historia, Estándar & Motores [ref]

Vamos a ver la tercera parte de novedades que nos trae la edición ES8 (ES2017) de JavaScript.

Importante: Hay que tener en cuenta que es posible que NO TODOS los navegadores modernos admitan estas “nuevas” funcionalidades, por lo que te sugiero primero validar las versiones de los mismos antes de subir a producción. Si estas usando algún transpilador (ej. Babel) no deberás preocuparte.

Antes de comenzar te sugiero que primero leas:

  • JavaScript — Callbacks y Promises[ref]
  • JavaScript — Symbols & Generadores ES6 (Parte III)[ref] (generadores)
  • JavaScript — Cómo funciona el Runtime Environment — JRE)[ref]

ii. Nuevas Características

01. async/await [ref]/[ref]

Cuando introdujeron las promesas en ES6 el objetivo principal fue resolver el problema asíncrono (lo cual lograron), además de “resolver” el callback hell, aunque seamos sinceros agregaron complejidad que quizás en su momento no estábamos preparados (y ellos lo sabían) por lo que en la edición ES8 nos presentan async/await.

Una función asíncrona es una función declarada con la palabra clave async, donde nos va a permitir tener un comportamiento asíncrono basado en promesas con un estilo “más limpio” y sin bloquear el hilo de ejecución.

Las funciones asíncronas son una combinación entre promesas y generadores, digamos que son una abstracción de mayor nivel sobre las promesas, ya que async/await están basados en promesas y una de sus ventajas es que hace que el código parezca síncrono.

Síncrono: Es cuando se ejecuta una operación de entrada/salida de manera secuencial es decir, se debe esperar a que se complete para poder procesar el resultado.

Sintaxis —

Donde:

  • name [obligatorio] — Nombre de la función (también puede ser una función de expresión)
  • param [opcional] — Los argumentos que se le pasan a la función
  • statements [obligatorio] — La lógica de la función, se sugiere que siempre tenga un return aunque no es obligatorio

Ejemplo 1 —

Podemos observar que en la línea 5 nos retorna una promesa completada, al ser considerada una promesa, debemos tratarla como tal entonces si queremos retornar el valor:

Lo interesante es que podemos tratar el returncon Promise.resolve:

Podemos observar que tiene un comportamiento similar a una promesa es decir envuelto en Promise.resolve, PERO es importante saber que una promesa y una función asíncrona NO son equivalentes ya que ambas regresan referencias diferentes.

Para hacer mas orgánico la espera de una función asíncrona se utiliza el operador await

Donde:

  • [rv] [opcional] — Valor cumplido de la promesa
  • Promise [obligatorio] — Cualquier promesa a esperar

Importante: El operador await solo se puede usar dentro de una función asíncrona y NO fuera de ella (por ahora 😉) , así como tampoco puedes usarla dentro de una función regular ya que de lo contrario lanzará un SyntaxError

Cuando se desea llamar una función asíncrona solo se debe anteponer await, el código de llamada se detendrá hasta que la función retorne un resultado.

Ejemplo 2—

Podemos observar que se ha cambiado el .then por await, es decir cada que ocupamos await este automáticamente se encargará de extraer el valor resuelto, por lo que ya no es necesario utilizar then

El mismo ejemplo con Promise.resolve

Ejemplo 3—

Hagamos un ejemplo un poco mas complejo, y siguiendo las reglas del await

Podemos observar que:

  • En la línea 2 se esta creando una promesa para que sea resuelta en 2 seg.
  • En la línea 6 por medio de await se invoca myPromise donde va a esperar a que se resuelva, una vez resuelto, asigna la promesa resuelta a la variable message
  • Finalmente en la línea 8 mostramos el resultado

Ejemplo 4—

Veamos primero un ejemplo con fetch y then, donde vamos a consumir la api de themoviedb[ref]:

Ahora ese mismo ejemplo, con async/await :

Podemos observar en la animación de arriba que:

  • En la línea 12 hace la petición al servicio por medio del fetch, y con el await va a esperar a que se resuelva la petición, para almacenar el valor en la variable response
  • En la línea 13 espera a que la respuesta se convierta en un JSON, una vez que se resuelve almacena el resultado en la variable movie
  • Finalmente en la línea 14 retornamos en consola la variable movie

Manejo de errores —

Hasta este momento sabemos cómo ocupar async/await siempre y cuando la promesa sea resuelta, pero… ¿qué pasa cuando la promesa no se resuelve?

Para lidiar con los errores, tenemos dos formas de hacerlo:

  • El método catch() — Al tratarse de una promesa este método nos va a permitir manejar y tratar el error cuando es rechazado.
  • La declaración try...catch — Usando try para envolver el código y catch para manejar cualquier error que ocurra, es decir no solo el error de la promesa.

Desventajas de usar async/await

Sabemos que await pausa la ejecución de la función asíncrona en la que se encuentra (que es el comportamiento esperado), esto quiere decir que no podemos terminar su ejecución hasta que la promesa termine (resuelva o rechace).

Ejemplo 5—

Veamos el siguiente ejemplo:

Podemos observar que:

  • En la línea 1, tenemos una función que se va a encargar promesas falsas
  • En la línea 10 creamos un timestamp cuando la función es invocada
  • En la línea 11 creamos una promesa, donde await va a esperar a que se resuelva, esta tardara 1000ms
  • En la línea 12 creamos una promesa, donde await va a esperar a que se resuelva, esta tardara 800ms
  • En la línea 13 creamos una promesa, donde await va a esperar a que se resuelva, esta tardara 3000ms
  • En la línea 15 creamos un timestamp cuando todas las promesas se han resuelto
  • En la línea 17 retornamos un mensaje con el tiempo que se ha tardado, que en este caso ha sido de 4.8s

Es importante entender que anteponer await a las promesas serán ejecutadas en secuencia (en cadena), es decir hasta que no se cumpla una, NO pasará a la siguiente.

Si quieres ejecutar todas las promesas en paralelo, deberás utilizar el método Promise.all() (JavaScript — Callbacks y Promises[ref])

Ejemplo Final —

El siguiente es un ejemplo digamos… “complejo”, lo pongo entre comillas, ya que si tú has leído y comprendido las stories que te recomendé al principio [ref][ref][ref], entonces será un ejemplo sencillo. Veamos:

Sin ejecutar el código, ¿podrás decirme en que orden se ejecuta el código?, es decir, ¿cuál será el orden de salida de la consola?…

Si ejecutamos el código, la salida en consola es la siguiente:

Lo que esta pasando es lo siguiente:

Vamos paso a paso (haz clic en la imagen para agrandar):

Soporte [ref][ref] —

No olvidemos que… —

  • La palabra await solo pueda usarse dentro de una función asíncrona, es decir una función que tenga antepuesta la palabra async, de no tenerla nos dará un SyntaxError
  • Las funciones asíncronas también pueden definirse como expresiones.[ref]
  • await no puede ser finalizada hasta que se liquide la promesa.
  • JavaScript es un lenguaje de programación síncrono de un solo hilo
  • Depurar es más sencillo ya que la consola lo interpretará como un código síncrono.
  • Las funciones con async siempre devuelven una promesa.
  • Utilizar try...catch para el manejo de errores

--

--