Aprende leyendo en orden

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)
La función que pasas a then se ejecuta al final
Llamar adelay(100, …)Se devuelve unaPromise pendingPasar una funcióna seats.then(…)Esa función aúnno se llamaconsole.log de lalínea siguientePrimero sale elaviso de consulta100ms después dellamar a delayfulfilled; seejecuta la función
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.

Muestra un aviso cuando termine la conversión de cada uno de los dos videos. delay ya está declarado.

① En convertVideo, devuelve una Promise que quede fulfilled pasados ms milisegundos con un mensaje de la forma "Terminó la conversión de intro.mp4".

② Convierte intro.mp4 en 150ms y outro.mp4 en 80ms, y muestra cada mensaje según vaya llegando.

③ Debajo de ②, muestra "Puedes seguir editando mientras se convierte".

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

Editor JavaScript / TypeScript

Ejecutar el código para ver el resultado

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)
Cómo resolve y reject deciden el estado
Se crea con newPromise (pending)Pasar un valora resolvePasar un Errora rejectNo llamar aresolve ni rejectQuedafulfilledQuedarejectedSiguependingLlama a theny luego a finallyLlama a catchy luego a finallyNo se llaman then,catch ni finally
En las dos columnas de la izquierda, finally se llama después de then o de catch. Qué funciones se llaman depende de si se llamó a resolve o a reject.

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.

Consulta el saldo de una tarjeta de regalo. delay, balances y codes ya están declarados.

① En checkGiftCard, si el código no empieza por "GIFT-", rechaza la Promise de inmediato, sin esperar, con un Error cuyo mensaje sea "Formato de código no válido".

② En caso contrario, resuélvela pasados 100ms con un mensaje de la forma "Saldo: 3000 yenes".

③ Consulta cada código de codes y muestra el mensaje resultante.

④ Tanto si sale bien como si falla, muestra al final "Se ocultó el indicador de consulta".

Editor JavaScript / TypeScript

Ejecutar el código para ver el resultado

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, then no espera
returnsendMail(orderId)Espera a que seenvíe el correoresult recibe eltexto del envíoMuestra el fintras el envíoSolo llama asendMail(orderId)El siguiente thenno esperaresult esundefinedMuestra el finantes del envío
Solo la fila de arriba espera a que se envíe el correo antes de llamar a la siguiente función. El siguiente then espera al resultado solo cuando devuelves la Promise con return.

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);.

Paga en yenes un artículo de 12 dólares en una tienda del extranjero. delay, dollars, loadRate y chargeCard ya están declarados.

① Con el tipo de cambio, convierte dollars a yenes, muestra "Importe convertido: 1800 yenes" y pasa el importe al siguiente paso.

② Cobra ese importe y no sigas adelante hasta que se decida el resultado.

③ Muestra el resultado del pago.

④ Si algo falla por el camino, muestra "No se pudo realizar el pago: " seguido del mensaje del Error.

Editor JavaScript / TypeScript

Ejecutar el código para ver el resultado

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));
El orden en que se pasan no es el orden en que terminan
Índice 0loadSales()184000 a los 100mstermina 3.ªÍndice 1loadOrders()42 a los 50mstermina 1.ªÍndice 2loadInquiries()3 a los 80mstermina 2.ª
Las tres terminan a los 50ms, 80ms y 100ms, en ese orden. then recibe [184000, 42, 3], en el orden en que se pasaron (columna izquierda).

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 pasanPromise que devuelve Promise.allFunción que se llama y valor que recibe
Todas fulfilledQueda fulfilled cuando se cumple la últimathen recibe un array en el orden en que se pasaron
Una queda rejectedQueda rejected en cuanto esa queda rejectedcatch recibe ese Error
Dos o más quedan rejectedQueda rejected en cuanto la primera queda rejectedcatch recibe solo el primer Error
Las demás quedan fulfilled, pero una sigue pendingSigue pendingNo se llama ni a then ni a catch

Pide una cotización conjunta para un viaje. Las cuatro funciones cuyo nombre empieza por quote ya están declaradas.

① En estimateTrip, espera hasta que lleguen todas las Promises que recibe.

② Muestra la suma de los precios como "Total: 68500 yenes".

③ Si aunque sea una falla, muestra "No se pudo cotizar: " seguido del mensaje del Error.

④ Pide una cotización con el vuelo, el hotel y el auto de alquiler, y luego otra cambiando el auto de alquiler por quoteFullCar, que no tiene disponibilidad.

Editor JavaScript / TypeScript

Ejecutar el código para ver el resultado
QUIZ

Verificación de conocimientos

Responde cada pregunta una a una.

Pregunta 1Si delay(100, "A").then((v) => console.log(v)); va seguido de console.log("B");, ¿en qué orden se muestra la salida?

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")])?