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?
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
showParking()avanza hasta el primerawaitde dentro y devuelve una Promise pending- No se llega a
console.log(message)hasta que esa Promise sea fulfilled
- Muestra
Buscando un lugar libre await assignSpot()— se pausa hasta que sea fulfilled, 100ms después- Pasa a la siguiente línea cuando
spotya contiene"B2-17" - Con el mensaje de
return, la Promise que devolvió pasa a fulfilled
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.
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
}
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.
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)
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ón | Cómo escribirlo | Por qué |
|---|---|---|
| Cargar el stock, la cantidad de reseñas y la fecha de entrega | await 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 pedido | Un 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 orden | await en el cuerpo de for...of (en serie) | En paralelo, los elementos pueden terminar en un orden distinto al del array |
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
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.
Verificación de conocimientos
Responde cada pregunta una a una.
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?