Async/await
Async/await
Existe una sintaxis especial para trabajar con promesas de una forma más confortable, llamada “async/await”. Es
sorprendentemente fácil de entender y usar.
Funciones async
Comencemos con la palabra clave async . Puede ser ubicada delante de una función como aquí:
La palabra “async” ante una función significa solamente una cosa: que la función siempre devolverá una
promesa. Otros valores serán envueltos y resueltos en una promesa automáticamente.
Por ejemplo, esta función devuelve una promesa resuelta con el resultado de 1 ; Probémosla:
Entonces, async se asegura de que la función devuelva una promesa, o envuelve las no promesas y las
transforma en una. Bastante simple, ¿correcto? Pero no solo eso. Hay otra palabra, await , que solo
trabaja dentro de funciones async y es muy interesante.
Await
1 // funciona solamente dentro de funciones async
2 let value = await promise;
await hace que JavaScript espere hasta que la promesa responda y devuelve su resultado.
La ejecución de la función es pausada en la línea (*) y se reanuda cuando la promesa responde, con result
volviéndose su resultado. Entonces el código arriba muestra “¡Hecho!” en un segundo.
Enfaticemos: await literalmente suspende la ejecución de la función hasta que se establezca la promesa, y
luego la reanuda con el resultado de la promesa. Eso no cuesta ningún recurso de CPU, porque el motor de
JavaScript puede hacer otros trabajos mientras tanto: ejecutar otros scripts, manejar eventos, etc.
Es simplemente una sintaxis más elegante para tener el resultado de una promesa que promise.then , es
más fácil de leer y de escribir.
1 function f() {
2 let promise = Promise.resolve(1);
3 let result = await promise; // Syntax error
4 }
Es posible que obtengamos este error si olvidamos poner async antes de una función. Como se dijo,
“await” solo funciona dentro de una función async .
Bien limpio y fácil de leer, ¿no es cierto? Mucho mejor que antes.
Los navegadores modernos permiten await en el nivel superior de los módulos
En los navegadores modernos, await de nivel superior funciona, siempre que estamos dentro de un
módulo. Cubriremos módulos en el artículo Módulos, introducción.
Por ejemplo:
1 (async () => {
2 let response = await fetch('/article/promise-chaining/user.json');
3let user = await response.json();
4 ...
5 })();
await acepta “thenables”
Tal como promise.then , await nos permite el uso de objetos “thenable” (aquellos con el método then
). La idea es que un objeto de terceras partes pueda no ser una promesa, sino compatible con una: si
soporta .then , es suficiente para el uso con await .
Aquí hay una demostración de la clase Thenable ; el await debajo acepta sus instancias:
1 class Thenable {
2 constructor(num) {
3this.num = num;
4 }
5 then(resolve, reject) {
6 alert(resolve);
7 // resuelve con this.num*2 después de 1000ms
8 setTimeout(() => resolve(this.num * 2), 1000); // (*)
9}
10 }
11
12 async function f() {
13 // espera durante 1 segundo, entonces el resultado se vuelve 2
14 let result = await new Thenable(1);
15 alert(result);
16 }
17
18 f();
Si await obtiene un objeto no-promesa con .then , llama tal método proveyéndole con las funciones
incorporadas resolve y reject como argumentos (exactamente como lo hace con ejecutores Promise
regulares). Entonce await espera hasta que une de ellos es llamado (en el ejemplo previo esto pasa en la
línea (*) ) y entonces procede con el resultado.
1 class Waiter {
2 async wait() {
3 return await Promise.resolve(1);
4 }
5 }
6
7 new Waiter()
8 .wait()
9 .then(alert); // 1 (lo mismo que (result => alert(result)))
El significado es el mismo: Asegura que el valor devuelto es una promesa y habilita await .
Manejo de Error
Si una promesa se resuelve normalmente, entonces await promise devuelve el resultado. Pero en caso de
rechazo, dispara un error, tal como si hubiera una instrucción throw en aquella línea.
Este código:
En situaciones reales, la promesa tomará algún tiempo antes del rechazo. En tal caso habrá un retardo antes
de que await dispare un error.
Podemos atrapar tal error usando try..catch , de la misma manera que con un throw normal:
En el caso de un error, el control salta al bloque catch . Podemos también envolver múltiples líneas:
Si no tenemos try..catch , entonces la promesa generada por el llamado de la función async f() se vuelve
rechazada. Podemos añadir .catch para manejarlo:
Si olvidamos añadir .catch allí, obtendremos un error de promesa no manejado (visible en consola). Podemos
atrapar tales errores usando un manejador de evento global unhandledrejection como está descrito en el
capítulo Manejo de errores con promesas.
async/await y promise.then/catch
Cuando usamos async/await , raramente necesitamos .then , porque await maneja la espera por
nosotros. Y podemos usar un try..catch normal en lugar de .catch . Esto usualmente (no siempre) es
más conveniente.
Pero en el nivel superior del código, cuando estamos fuera de cualquier función async , no estamos
sintácticamente habilitados para usar await , entonces es práctica común agregar .then/catch para
manejar el resultado final o errores que caigan a través, como en la línea (*) del ejemplo arriba.
En caso de error, se propaga como es usual, desde la promesa que falla a Promise.all , y entonces se
vuelve una excepción que podemos atrapar usando try..catch alrededor del llamado.
Resumen
El comando async antes de una función tiene dos efectos:
1. Hace que siempre devuelva una promesa.
2. Permite que sea usado await dentro de ella.
El comando await antes de una promesa hace que JavaScript espere hasta que la promesa responda. Entonces:
1. Si es un error, la excepción es generada — lo mismo que si throw error fuera llamado en ese mismo lugar.
2. De otro modo, devuelve el resultado.
Juntos proveen un excelente marco para escribir código asincrónico que es fácil de leer y escribir.
Con async/await raramente necesitamos escribir promise.then/catch , pero aún no deberíamos olvidar
que están basados en promesas porque a veces (ej. como en el nivel superior de código) tenemos que usar esos
métodos. También Promise.all es adecuado cuando esperamos por varias tareas simultáneas.
Tareas
Rescribir este código de ejemplo del capítulo Encadenamiento de promesas usando async/await en vez de
.then/catch :
1 function loadJson(url) {
2 return fetch(url)
3 .then(response => {
4 if (response.status == 200) {
5return response.json();
6 } else {
7 throw new Error(response.status);
8 }
9 });
10 }
11
12 loadJson('https://javascript.info/no-such-user.json')
13.catch(alert); // Error: 404
solución
Debajo puedes encontrar el ejemplo “rethrow”. Rescríbelo usando async/await en vez de .then/catch .
Y deshazte de la recursión en favor de un bucle en demoGithubUser : con async/await , que se vuelve fácil de
hacer.
solución
Tenemos una función “regular” llamada f . ¿Cómo llamar la función async , wait() y usar su resultado dentro
de f ?