Aprende leyendo en orden

async / await — Esperar el resultado de una Promise línea por línea

Espera el resultado de una Promise con async / await, captura fallos con try / catch, compara la espera en serie con Promise.all y mira qué pasa si olvidas await.

Con el then de una Promise, el valor solo te llega como argumento de una función. Si quieres usar el primer resultado en un tercer paso, tienes que anidar funciones o copiar el valor en una variable de fuera, y el código se vuelve difícil de leer.

En este artículo verás async / await, que espera un resultado y luego sigue con la siguiente línea, y también cómo esperar varias Promises.

Escribir los pasos de arriba abajo — funciones async y await

Supón que reservas un lugar libre en un estacionamiento y quieres poner el número asignado en un mensaje. Con then, todo lo que usa ese número tiene que ir dentro de la función que le pasas a then, y el código que escribas debajo, fuera de esa función, se ejecuta antes de que llegue el número.

Dentro de una función async (una función declarada con async que siempre devuelve una Promise al llamarla) puedes usar await. await pausa solo el resto de esa función hasta que el resultado se decide y luego saca el valor fulfilled. Mientras está en pausa, quien la llamó recibe enseguida una Promise en estado pending.

// Devuelve una Promise que pasa a fulfilled con value tras ms milisegundos (igual que en el artículo anterior)
function delay(ms, value) {
  return new Promise((resolve) => {
    setTimeout(() => resolve(value), ms);
  });
}
const assignSpot = () => delay(100, "B2-17");

// await se escribe dentro de una función marcada con async
async function showParking() {
  console.log("Buscando un lugar libre");     // Buscando un lugar libre
  const spot = await assignSpot();            // No pasa a la siguiente línea hasta que sea fulfilled
  console.log(`Lugar ${spot} reservado`);     // Lugar B2-17 reservado
  return `Puedes estacionar en el lugar ${spot}`;  // La Promise ya devuelta pasa a fulfilled con este mensaje
}

// Quien llama también espera la Promise devuelta con await
const message = await showParking();
console.log(message);                         // Puedes estacionar en el lugar B2-17
Qué espera cada uno de los dos await
Quien llama — const message = await showParking()
  • showParking() avanza hasta el primer await de dentro y devuelve una Promise pending
  • No se llega a console.log(message) hasta que esa Promise sea fulfilled
Dentro de async function showParking()
  • Muestra Buscando un lugar libre
  • await assignSpot() — se pausa hasta que sea fulfilled, 100ms después
  • Pasa a la siguiente línea cuando spot ya contiene "B2-17"
  • Con el mensaje de return, la Promise que devolvió pasa a fulfilled
El de dentro espera el resultado de assignSpot; el de fuera espera a que termine la función. El valor que devuelve return termina en message.

Una función async sin return también devuelve una Promise, que pasa a fulfilled con undefined cuando el cuerpo llega al final. Aunque no necesites ningún valor, si pones await en la llamada, la siguiente línea espera a que termine el trabajo de dentro.

await fuera de una función solo funciona en algunos sitios

El entorno de ejecución de esta página envuelve todo tu código en una función async, así que también puedes escribir await fuera de una función, como en const message = await showParking();. En un archivo JavaScript normal, un await fuera de una función async puede dar un SyntaxError. Cuando escribas código fuera de esta página, pon los await dentro de funciones async.

Envía el aviso de una cita en una peluquería. delay, confirmBooking y registerReminder ya están declarados.

① Convierte notifyBooking en una función que pueda esperar resultados dentro.

② Espera la fecha y hora confirmadas y muestra «Tu cita: 14 de septiembre a las 15:00».

③ Registra un recordatorio para esa fecha y hora y muestra el resultado.

④ Fuera de la función, espera a que termine y muestra «Tu reserva está completa».

(Si se ejecuta correctamente, aparecerá una explicación.)

Editor JavaScript / TypeScript

Ejecutar el código para ver el resultado

Tratar los fallos como excepciones — await y try / catch

Si apartas un asiento para un concierto y luego pagas con tarjeta, el pago puede fallar. Cuando sacas los valores con await no construyes una cadena de then, así que no puedes capturar el fallo con un .catch al final, como en el artículo anterior.

Si haces throw dentro de una función async, la Promise que devuelve esa función pasa a rejected. Cuando haces await de una Promise rejected, se lanza el mismo Error en la línea de ese await, así que puedes envolver esa línea tal cual en try / catch.

// delay es igual que en el código de la primera sección
const holdSeat = () => delay(100, "S-204");
const sendReceipt = (seatId) => delay(100, `Recibo enviado para el asiento ${seatId}`);

// Si haces throw dentro de una función async, la Promise que devuelve pasa a rejected
async function payByCard() {
  await delay(100);                          // No usa ningún valor, solo espera 100ms
  throw new Error("La tarjeta está vencida");
}

try {
  const seatId = await holdSeat();
  console.log(`Asiento ${seatId} apartado`);         // Asiento S-204 apartado
  await payByCard();                                 // Es rejected, así que esta línea lanza una excepción
  const receipt = await sendReceipt(seatId);         // De aquí hacia abajo no se ejecuta nada
  console.log(receipt);
} catch (error) {
  console.log(`No se pudo comprar: ${error.message}`);  // No se pudo comprar: La tarjeta está vencida
}
Qué línea se ejecuta después si sale bien y si falla
Si el pagosale bienawait payByCard()terminaSigue consendReceiptEnvíael reciboSi la tarjetaestá vencidaLa línea deawait payByCard()lanza un errorSe saltael resto de trycatch (error)muestra el mensaje
La fila de arriba sigue con la siguiente línea; la de abajo se salta el resto de try y pasa a catch. Hacia dónde sigue depende del estado de la Promise que esperaste con await.

catch solo recibe las excepciones que se lanzan dentro de las llaves de try, así que pon dentro de try cualquier línea con await que pueda fallar. La limpieza que debe hacerse tanto si sale bien como si falla va en un bloque finally, después de try / catch.

Pide la renovación del préstamo de unos libros de la biblioteca. delay, loans y renewLoan ya están declarados.

① En requestRenewal, espera el resultado de renewLoan y muestra el mensaje que recibes.

② Si falla, muestra «No se puede renovar: » seguido del mensaje del Error.

③ Tanto si sale bien como si falla, termina mostrando una línea como «Trámite terminado: Introducción a la estadística».

④ Espera a que termine el primer libro de loans antes de pedir el segundo.

Editor JavaScript / TypeScript

Ejecutar el código para ver el resultado

Empezar tres llamadas a la vez — en serie frente a Promise.all

Supón que una página de producto, al abrirse, consulta las unidades en stock, la cantidad de reseñas y la fecha estimada de entrega. Si escribes un await por línea, cada consulta espera a que termine la anterior para empezar, aunque ninguna línea use el resultado anterior. Consultas que no tienen nada que ver entre sí terminan haciendo fila.

Empezar la siguiente tarea solo cuando termina la anterior se llama ejecución en serie; empezar varias a la vez y luego esperar se llama ejecución en paralelo. Para ejecutarlas en paralelo, llama primero a las tres funciones para obtener sus Promises, pásalas a Promise.all, que viste en el artículo anterior, y espera con await en lugar de then.

// delay es igual que en el código de la primera sección
const loadStock = () => delay(120, 8);
const loadReviews = () => delay(50, 23);
const loadArrival = () => delay(90, "13 de septiembre");

// En serie: llama a la siguiente cuando llega cada una (260ms hasta tenerlas todas: 120 + 50 + 90)
const stock = await loadStock();
const reviews = await loadReviews();
const arrival = await loadArrival();
console.log([stock, reviews, arrival].join(" / "));  // 8 / 23 / 13 de septiembre

// En paralelo: llama primero a las tres y luego espera (están todas cuando llega la más lenta, 120ms)
const promises = [loadStock(), loadReviews(), loadArrival()];
const results = await Promise.all(promises);
console.log(results.join(" / "));                   // 8 / 23 / 13 de septiembre (el mismo resultado)
Cuándo llegan los resultados, en serie y en paralelo
En serie 0mspide el stock120ms llega stockpide las reseñas170ms lleganlas reseñaspide la fecha260msestán las 3En paralelo 0mspide las 350msllegan las reseñas90msllega la fecha120ms llega stockestán las 3
En la fila de abajo las tres empiezan a los 0ms y están todas a los 120ms, lo que tarda la más lenta. Con un await por línea, la espera total es de 260ms.

Si escribes await en el cuerpo de un for...of, los elementos del array también se procesan en serie, uno por uno, y la espera total es la suma de todos. La tabla de abajo resume cuándo conviene elegir en serie y cuándo en paralelo.

SituaciónCómo escribirloPor qué
Cargar el stock, la cantidad de reseñas y la fecha de entregaawait con Promise.all (en paralelo)Ninguna usa el resultado de otra, así que pueden empezar juntas
Enviar un correo de confirmación después de obtener el número de pedidoUn await por línea (en serie)La segunda llamada necesita el resultado de la primera
Procesar los elementos de un array uno por uno, en ordenawait en el cuerpo de for...of (en serie)En paralelo, los elementos pueden terminar en un orden distinto al del array

Una app del clima carga el pronóstico de tres ciudades. Compara el orden en que llegan. delay, loadForecast y cities ya están declarados.

① Recorre cities en orden y espera a que llegue cada pronóstico antes de cargar la siguiente ciudad.

② Muestra «Se cargó una ciudad a la vez».

③ Ahora empieza a cargar las tres ciudades a la vez y espera a que estén todas.

④ Muestra los pronósticos unidos con « / ».

Editor JavaScript / TypeScript

Ejecutar el código para ver el resultado

Llamar sin await — lo que queda en la variable es una Promise

Supón que muestras el saldo de puntos de un cliente y le ofreces un beneficio si tiene 1000 puntos o más. Si olvidas await al llamar a una función async, no hay SyntaxError: simplemente obtienes un texto y una comparación incorrectos, y cuesta encontrar dónde está el error.

Una función async no devuelve el valor de return, sino una Promise que pasa a fulfilled con ese valor. Si tomas el resultado sin await, la variable contiene la Promise, y al insertarla en una template literal se muestra [object Promise] (así se ve una Promise convertida en cadena).

// delay es igual que en el código de la primera sección
async function loadPoints() {
  await delay(100);
  return 1200;                              // La Promise ya devuelta pasa a fulfilled con 1200
}

// Si olvidas await, points contiene una Promise
const points = loadPoints();
console.log(`Saldo de puntos: ${points}`);  // Saldo de puntos: [object Promise]
console.log(points >= 1000);                // false (una Promise convertida en número es NaN, así que la comparación no se cumple)

// Con await, la variable contiene el valor fulfilled
const balance = await loadPoints();
console.log(`Saldo de puntos: ${balance}`); // Saldo de puntos: 1200
console.log(balance >= 1000);               // true
points sigue siendo una Promise
points =loadPoints()Contiene unaPromise pendingPasa a fulfilleda los 100mspoints sigueconteniendola PromiseSe insertaen una cadenaSe muestra[object Promise]
Aunque pase a fulfilled, points nunca se reemplaza por 1200. Para obtener el valor necesitas await.

Si la salida muestra [object Promise], o una condición que debería cumplirse da false, revisa si la llamada a la función async lleva await. En la condición de un if, una Promise es un objeto y, por tanto, truthy, así que la condición se cumple sin importar qué valor devuelva la función.

Un fallo sin await nunca llega a catch

Si dentro de try llamas sin await a una función async que devuelve una Promise rejected, esa línea no lanza ninguna excepción y catch nunca se ejecuta. Nadie recibe el fallo, y el entorno de ejecución de esta página no muestra nada. Para recibirlo en catch, agrega await a la llamada.

Comprueba si los cupones vencieron e indica cuáles se pueden usar. delay, coupons e isExpired ya están declarados.

① Sin await, llama a isExpired con el primer cupón y usa el resultado como condición de un if.

② Si la condición se cumple, muestra «AUTUMN10 ya venció»; si no, muestra «AUTUMN10 se puede usar».

③ Recorre coupons en orden, espera cada resultado y luego haz la misma comprobación y muestra el mensaje.

Editor JavaScript / TypeScript

Ejecutar el código para ver el resultado
QUIZ

Verificación de conocimientos

Responde cada pregunta una a una.

Pregunta 1Dentro de try, ¿qué pasa con las líneas que están debajo de una línea que hace await de una Promise que pasa a rejected?

Pregunta 2Si haces await de tres llamadas que tardan 70ms, 30ms y 50ms, una por línea, ¿cuánto tarda aproximadamente en llegar todo?

Pregunta 3Si llamas sin await a una función async que termina en return 1200; y guardas el resultado en una variable, ¿qué contiene la variable?