Pregunta 1Si delay(100, "A").then((v) => console.log(v)); va seguido de console.log("B");, ¿en qué orden se muestra la salida?
Promise — recibir resultados asíncronos con then y catch
Recibe más tarde el resultado de una operación lenta con Promise: sus tres estados, then / catch / finally, el encadenamiento y Promise.all.
Algunas operaciones, como enviar un pedido o consultar el stock, tardan un rato en devolver un resultado. Para que la página no se quede congelada mientras tanto, este tipo de código se escribe de forma que pase enseguida a la línea siguiente y recoja el resultado más tarde. Esta forma de programar se llama programación asíncrona.
En este artículo verás Promise, que representa un resultado que se decide más tarde, y Promise.all, que espera a varias Promises a la vez.
Recibir un resultado más tarde — Promise y then
Imagina que la respuesta sobre la disponibilidad de asientos tarda 100ms en llegar. setTimeout (una función que llama a otra función una sola vez, pasados los milisegundos indicados) termina de inmediato y la ejecución pasa a la línea siguiente, así que tu función no puede devolver con return una respuesta que llega 100ms después.
En su lugar, devuelves una Promise (un objeto que representa un resultado que se decide más tarde), y la respuesta llega a través de ella. La función que pasas a new Promise((resolve) => { ... }) se llama de inmediato, y si dentro llamas a resolve(valor), la Promise pasa de pending (esperando un resultado) a fulfilled (cumplida con éxito). La función que pasas al método then se llama con ese valor en cuanto la Promise queda fulfilled.
// Devuelve una Promise que queda fulfilled con value pasados ms milisegundos
function delay(ms, value) {
return new Promise((resolve) => {
setTimeout(() => resolve(value), ms);
});
}
// Empieza la consulta (en este momento sigue pending)
const seats = delay(100, "Quedan 12 asientos");
// Pasa desde ya la función que se llamará cuando quede fulfilled
seats.then((message) => {
console.log(message); // Quedan 12 asientos (se muestra en 2.º lugar)
});
console.log("Consultando la disponibilidad de asientos"); // Consultando la disponibilidad de asientos (se muestra en 1.er lugar)
then solo guarda la función; no la llama en ese momento. La función que pasas se ejecuta solo cuando han terminado las líneas de abajo y la Promise queda fulfilled.Si escribes fuera de then el código que usa el resultado, se ejecuta antes de que el resultado se decida, así que el valor todavía no está disponible. Deja las líneas que usan el resultado dentro de la función que pasas a then, y pon lo que quieras hacer mientras esperas en las líneas que van después, fuera de then.
Añade al final una línea que espere a then
El ejecutor de código de esta página solo recoge la salida que se muestra antes de que termine la última línea. En un navegador o en Node.js, la función de then se ejecuta aunque nada la espere, pero aquí añades await delay(1000); al final para esperar un segundo. await (una sintaxis que no pasa a la línea siguiente hasta que se decide el resultado de una Promise) se trata en el siguiente artículo.
Gestionar los fallos — reject / catch / finally
La verificación del PIN de una tarjeta falla si el número es incorrecto. Si solo tienes resolve, un fallo llega como un valor más, igual que un éxito, así que la función que lo recibe necesita un if cada vez para distinguirlos.
La función que pasas a new Promise también puede recibir reject como segundo parámetro. Si la llamas, como en reject(new Error("mensaje")), la Promise queda rejected (fallida). La función que pasas a catch recibe ese Error cuando la Promise queda rejected, y la función que pasas a finally se llama al final, tanto si salió bien como si falló.
function verifyPin(pin) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (pin === "4821") {
resolve("PIN verificado");
} else {
// Para avisar de un fallo, pasa un Error a reject
reject(new Error("PIN incorrecto"));
}
}, 100);
});
}
// then, catch y finally se pueden encadenar uno tras otro (la siguiente sección explica cómo)
verifyPin("0000")
.then((message) => console.log(message)) // no se llama, porque queda rejected
.catch((error) => console.log(error.message)) // PIN incorrecto
.finally(() => console.log("Se vació el campo de entrada"));
// Se vació el campo de entrada (se muestra al final, tanto si sale bien como si falla)
Cuando una Promise queda rejected, las funciones de then encadenadas antes de catch se saltan, y se llama a la función de catch que va después. Este catch es un método de Promise, distinto de la sentencia try / catch. Si nunca llamas ni a resolve ni a reject, parece que el código se ha quedado colgado, sin salida y sin ningún error.
Ejecutar pasos en orden — encadenar then y return
Imagina que, después de confirmar un pedido, quieres enviar un correo de confirmación con el número de pedido que te devuelven. Si escribes la segunda petición dentro del primer then, y la tercera dentro de esa, la sangría se hace más profunda con cada paso y cada vez cuesta más leer el código.
then devuelve una nueva Promise, así que puedes enlazar llamadas como en .then(...).then(...). A esto se le llama encadenamiento (enganchar el siguiente then a la Promise que devuelve el then anterior). El valor que devuelves con return desde una función de then lo recibe la función del siguiente then.
// delay es el mismo que en el código de la primera sección
const placeOrder = () => delay(100, "A-2046");
const sendMail = (orderId) => delay(100, `Se envió un correo de confirmación para ${orderId}`);
placeOrder()
.then((orderId) => {
console.log(`Número de pedido: ${orderId}`); // Número de pedido: A-2046
return sendMail(orderId); // la cadena espera hasta que se envía el correo
})
.then((result) => {
console.log(result); // Se envió un correo de confirmación para A-2046
return "Todos los pasos completados"; // el valor pasa directamente a la siguiente función
})
.then((status) => console.log(status)); // Todos los pasos completados
Sin return, la función devuelve undefined, así que el siguiente then se ejecuta sin esperar a que se envíe el correo, y result vale undefined. No aparece ningún error; simplemente se pierde el resultado del envío, y por eso es un fallo fácil de pasar por alto.
Si añades llaves, no puedes omitir return
Una función flecha sin llaves, como .then((id) => sendMail(id)), devuelve tal cual la Promise de sendMail, así que el siguiente then espera. Si envuelves el cuerpo entre llaves para añadir una línea de registro a mitad de camino, vuelve a poner el return, como en return sendMail(id);.
Esperar varios resultados a la vez — Promise.all
Imagina que, al abrir el panel de administración, pides por separado las ventas del día, el número de pedidos y el número de consultas, y quieres mostrar el panel de resumen cuando hayan llegado los tres. Si pones un then distinto a cada una de las tres Promises, la que llega la última cambia cada vez, así que no hay un único sitio donde comprobar que ya llegaron todas.
Si llamas a las tres funciones y pasas las Promises resultantes a Promise.all (un método que recibe un array de Promises y espera hasta tener todos sus resultados), recibes los tres resultados a la vez en un array. Si aunque sea una queda rejected, se pasa a catch en lugar de a then.
// delay es el mismo que en el código de la primera sección
const loadSales = () => delay(100, 184000);
const loadOrders = () => delay(50, 42);
const loadInquiries = () => delay(80, 3);
// Las tres empiezan en cuanto se llaman las funciones. Promise.all espera a tenerlas todas
Promise.all([loadSales(), loadOrders(), loadInquiries()])
// Recibe el array de resultados desestructurándolo por posición
.then(([sales, orders, inquiries]) => {
// Se reciben en el orden escrito en el array, no en el orden en que terminaron
console.log(`Ventas: ${sales} yenes`); // Ventas: 184000 yenes
console.log(`Pedidos: ${orders}`); // Pedidos: 42
console.log(`Consultas: ${inquiries}`); // Consultas: 3
})
.catch((error) => console.log(error.message));
Promise.all por sí mismo no inicia ninguna petición; solo espera hasta tenerlas todas. La tabla de abajo muestra, según el estado de las Promises que se pasan, la Promise que devuelve Promise.all y qué función se llama.
| Estado de las Promises que se pasan | Promise que devuelve Promise.all | Función que se llama y valor que recibe |
|---|---|---|
| Todas fulfilled | Queda fulfilled cuando se cumple la última | then recibe un array en el orden en que se pasaron |
| Una queda rejected | Queda rejected en cuanto esa queda rejected | catch recibe ese Error |
| Dos o más quedan rejected | Queda rejected en cuanto la primera queda rejected | catch recibe solo el primer Error |
| Las demás quedan fulfilled, pero una sigue pending | Sigue pending | No se llama ni a then ni a catch |
Verificación de conocimientos
Responde cada pregunta una a una.
Pregunta 2Si encadenas .then(A).catch(B).finally(C) a una Promise que queda rejected, ¿cuáles se llaman?
Pregunta 3¿Qué array recibe el then de Promise.all([delay(100, "A"), delay(50, "B")])?