En la web, muchas cosas tienden a llevar mucho tiempo: si consultas una API, puede llevar un tiempo recibir una respuesta. Por lo tanto, la programación asíncrona es una habilidad esencial para los desarrolladores.

  Cuando trabajamos con operaciones asíncronas en JavaScript, a menudo escuchamos el término Promise (Promesa). Pero puede resultar complicado entender cómo funcionan y cómo utilizarlos.

  A diferencia de muchos tutoriales de programación tradicionales, en este tutorial aprenderemos con la práctica. Completaremos cuatro tareas al final del artículo:

  • Tarea 1: explicación de los conceptos básicos de la promesa usando mi cumpleaños.
  • Tarea 2: construye un juego de adivinanzas.
  • Tarea 3: obtener información del país de una API.
  • Tarea 4: buscar países vecinos de un país.

Si deseas seguir adelante, asegúrate de descargar los recursos aquí: https://bit.ly/3m4bjWI

Tarea 1: explicación de los conceptos básicos de la promesa usando mi cumpleaños

Alt Text

  Mi amiga Kayo promete hacer una torta para mi cumpleaños en dos semanas.

  Si todo va bien, y Kayo no se enferma, tendremos una cierta cantidad de tortas. (Las tortas son contables en este tutorial ?). De lo contrario, si Kayo se enferma, no tendremos tortas.

  De cualquier manera, todavía vamos a tener una fiesta. Para esta primera tarea, traduciremos esta historia en código. Primero, creemos una función que devuelva una Promise:

const miCumple = (KayoSeEnferma) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (!KayoSeEnferma) {
        resolve(2);
      } else {
        reject(new Error("Estoy triste"));
      }
    }, 2000);
  });
};

  En JavaScript, podemos crear una nueva Promise con new Promise(), que toma una función como argumento:(resolve, reject) => {}.

  En esta función, resolve(resolver) y reject(rechazar) son funciones callback, que se proporcionan de forma predeterminada en JavaScript.

  Echemos un vistazo más de cerca al código anterior.

  Cuando ejecutamos la función miCumple, después de 2000 ms:

  • Si Kayo no está enferma, ejecutamos resolve con 2 como argumento.
  • Si Kayo está enferma, ejecutamos el rejectcon un new Error("Estoy triste") como argumento. Aunque puedes pasar cualquier cosa a reject como argumento, se recomienda pasarle un objeto Error.

  Ahora, porque miCumple() retorna una Promise, tenemos acceso a los métodos then(entonces), catch(atrapa), yfinally (finalmente).

  Y también tenemos acceso a los argumentos que se pasaron a resolve y reject antes en ese then y catch.

  Echemos un vistazo más de cerca al código.

  Si Kayo no está enferma:

miCumple(false)
  .then((result) => {
    console.log(`Yo tengo ${result} tortas`); // En la consola: Yo tengo 2 tortas  
  })
  .catch((error) => {
    console.log(error); // No se ejecuta
  })
  .finally(() => {
    console.log("Fiesta"); // Aparece en la consola no importa qué: Fiesta
  });

  Si Kayo está enferma:

miCumple(true)
  .then((result) => {
    console.log(`Yo tengo ${result} tortas`); // No se ejecuta 
  })
  .catch((error) => {
    console.log(error); // En consola: Error: Estoy triste
  })
  .finally(() => {
    console.log("Fiesta"); // Aparece en la consola no importa qué: Fiesta
  });

  Muy bien, a estas alturas, espero que tengas la idea básica de Promise. Pasemos a la tarea 2.

Tarea 2: construye un juego de adivinanzas

  Los requerimientos:

  • Historia de usuario: Un usuario puede introducir un número.
  • Historia de usuario: El sistema elige un número aleatorio del 1 al 6.
  • Historia de usuario: Si el número de usuario es igual al número aleatorio, le da al usuario 2 puntos.
  • Historia de usuario: Si el número del usuario es diferente al número aleatorio por 1, le da al usuario 1 punto. De otra manera, le da al usuario 0 puntos.
  • Historia de usuario: El usuario puede jugar tanto como quiera.

  Para las primeras 4 historias de usuario, crearemos una función insertaNum y retorna una Promise:

const insertaNum = () => {
  return new Promise((resolve, reject) => {
    // Empecemos desde aquí...
  });
};

  Lo primero que debemos hacer es pedirle un número al usuario y elegir un número aleatorio entre 1 y 6:

const insertaNum = () => {
  return new Promise((resolve, reject) => {
    const numUsuario = Number(window.prompt("Introduce un número (1 - 6):")); 
      // Pide al usuario que introduzca un número
      
    const aleatorio = Math.floor(Math.random() * 6 + 1); 
      // Elige un número aleatorio del 1 al 6
  });
};

  Ahora, numUsuario puede ingresar un valor que no es un número. Si es así, llamemos a la función reject con un error:

const insertaNum = () => {
  return new Promise((resolve, reject) => {
    const numUsuario = Number(window.prompt("Introduce un número (1 - 6):")); 
      // Pide al usuario que introduzca un número
      
    const aleatorio = Math.floor(Math.random() * 6 + 1); 
      // Elige un número aleatorio del 1 al 6

    if (isNaN(numUsuario)) {
      reject(new Error("Tipo de entrada incorrecta")); 
        // Si el usuario introduce un valor que no es un número, 
        // ejecuta reject con un error
    }
  });
};

  Lo siguiente que queremos hacer es verificar si el numUsuario es igual a aleatorio, si es así, queremos darle al usuario 2 puntos y podemos ejecutar la función resolve pasando un objeto {puntos: 2, aleatorio}. Observa que también queremos saber el número en aleatorio cuando se resuelva la promesa.

  Si él numUsuario es diferente de aleatorio por uno, entonces le damos al usuario 1 punto. De lo contrario, le damos al usuario 0 puntos:

return new Promise((resolve, reject) => {
  const numUsuario = Number(window.prompt("Introduce un número (1 - 6):")); 
    // Pide al usuario que introduzca un número
  const aleatorio = Math.floor(Math.random() * 6 + 1); 
    // Elige un número aleatorio del 1 al 6

  if (isNaN(numUsuario)) {
    reject(new Error("Tipo de entrada incorrecta")); 
        // Si el usuario introduce un valor que no es un número, 
        // ejecuta reject con un error
  }

  if (numUsuario === aleatorio) {
    // Si el número del usuario coincide con el número aleatorio, 
    // retorna 2 puntos
    resolve({
      puntos: 2,
      aleatorio,
    });
  } else if (numUsuario === aleatorio - 1 || numUsuario === aleatorio + 1) {
    // Si el número del usuario es diferente al número aleatorio por 1, 
    // retorna 1 punto
    resolve({
      puntos: 1,
      aleatorio,
    });
  } else {
    // Si no, retorna 0 puntos
    resolve({
      puntos: 0,
      aleatorio,
    });
  }
});

  Muy bien, también creemos otra función para preguntar si el usuario quiere continuar el juego:

const continuarJuego = () => {
  return new Promise((resolve) => {
    if (window.confirm("¿Quieres continuar?")) { 
        // Pregunta si el usuario quiere continuar el juego
        // con un modal de confirmación
      resolve(true);
    } else {
      resolve(false);
    }
  });
};

  Observa que creamos una Promise, pero no utiliza la función callback reject. Esto está totalmente bien.

  Ahora creemos una función para manejar la suposición:

const suponer = () => {
  insertaNum() // Esto retorna una Promesa
    .then((result) => {
      alert(`Dado: ${result.aleatorio}: obtuviste ${result.puntos} puntos`); 
      // Cuando resolve se ejecuta, obtenemos los puntos
      // y el número aleatorio
      
      // Vamos a preguntarle al usuario si quiere continuar el juego
      continuarJuego()
          .then((result) => {
                if (result) {
                  suponer(); // Si sí, ejecutamos suponer() de nuevo
                } else {
                  alert("Terminó el juego"); // Si no, mostramos una alerta
                }
          });
    })
    .catch((error) => alert(error));
};

suponer(); // Ejecuta la función suponer.

  Aquí cuando llamamos suponer(), insertaNum() ahora retorna una Promise:

  • Si la Promise es resuelta, llamamos al método then y mostramos un mensaje de alerta. También preguntamos si el usuario quiere continuar.
  • Si la Promise es rechazada, mostramos un mensaje de alerta con el error.

  Como puedes ver, el código es algo difícil de leer.

  Refactoricemos la función suponer un poco, utilizando la sintáxis async/await:

const suponer = async () => {
  try {
    const result = await insertaNum(); 
      // En lugar del método 'then', podemos obtener el resultado 
      // directamente, poniendo 'await' antes de la promesa
    alert(`Dado: ${result.aleatorio}: obtuviste ${result.puntos} puntos`);

    const estaContinuando = await continuarJuego();

    if (estaContinuando) {
      suponer();
    } else {
      alert("Terminó el juego");
    }
  } catch (error) { 
      // En lugar del método 'catch', podemos usar la sintáxis 'try/catch'
    alert(error);
  }
};

  Puedes ver que creamos una función async, colocando async antes de las llaves. Entonces en la función async:

  • En lugar del método then, podemos obtener los resultados directamente , poniendo await antes de la promesa.
  • En lugar del método catch, podemos utilizar la sintáxis try/catch.

  Aquí está todo el código para esta tarea, de nuevo, para tu referencia:

return new Promise((resolve, reject) => {
  const numUsuario = Number(window.prompt("Introduce un número (1 - 6):")); 
    // Pide al usuario que introduzca un número
  const aleatorio = Math.floor(Math.random() * 6 + 1); 
    // Elige un número aleatorio del 1 al 6

  if (isNaN(numUsuario)) {
    reject(new Error("Tipo de entrada incorrecta")); 
        // Si el usuario introduce un valor que no es un número, 
        // ejecuta reject con un error
  }

  if (numUsuario === aleatorio) {
    // Si el número del usuario coincide con el número aleatorio, 
    // devuelve 2 puntos
    resolve({
      puntos: 2,
      aleatorio,
    });
  } else if (numUsuario === aleatorio - 1 || numUsuario === aleatorio + 1) {
    // Si el número del usuario es diferente al número aleatorio por 1, 
    // devuelve 1 punto
    resolve({
      puntos: 1,
      aleatorio,
    });
  } else {
    // Si no, devuelve 0 puntos
    resolve({
      puntos: 0,
      aleatorio,
    });
  }
});

const continuarJuego = () => {
  return new Promise((resolve) => {
    if (window.confirm("¿Quieres continuar?")) { 
        // Pregunta si el usuario quiere continuar el juego
        // con un modal de confirmación
      resolve(true);
    } else {
      resolve(false);
    }
  });
};


const suponer = async () => {
  try {
    const result = await insertaNum(); 
      // En lugar del método 'then', podemos obtener el resultado 
      // directamente, poniendo 'await' antes de la promesa
    alert(`Dado: ${result.aleatorio}: obtuviste ${result.puntos} puntos`);

    const estaContinuando = await continuarJuego();

    if (estaContinuando) {
      suponer();
    } else {
      alert("Terminó el juego");
    }
  } catch (error) { 
      // En lugar del método 'catch', podemos usar la sintaxis 'try/catch'
    alert(error);
  }
};

  Muy bien, hemos terminado con la segunda tarea. Pasemos a la tercera.

Tarea 3: buscar información de un país desde una API

  Verás que las Promise se usan mucho al obtener datos de una API.

  Si abres https://restcountries.eu/rest/v2/alpha/col en una nueva ventana, verás los datos del país en formato JSON.

  Utilizando el Fetch API, podemos buscar los datos con:

const buscarDatos = async () => {
  const res = await fetch("https://restcountries.eu/rest/v2/alpha/col"); 
    // fetch() retorna una promesa, así que necesitamos esperar por ella
    
  const pais = await res.json(); 
    // res es ahora una respuesta HTTP, por lo que 
    //necesitamos llamar a res.json()

  console.log(pais); // Los datos de Colombia se registrarán en la consola
};

buscarDatos();

  Ahora que tenemos los datos del país que queremos, pasemos a la última tarea.

Tarea 4: buscar países vecinos de un país

  Si abres la tarea 4, verás que tenemos una función buscarPais, que obtiene los datos del endpoint: https://restcountries.eu/rest/v2/alpha/${alpha3Code} donde alpha3code es el código del país .

  También verás que detectará cualquier error que pueda ocurrir al obtener los datos.

// Tarea 4: obtener los países vecinos de Colombia

const buscarPais = async (alpha3Code) => {
  try {
    const res = await fetch(
      `https://restcountries.eu/rest/v2/alpha/${alpha3Code}`
    );

    const data = await res.json();

    return data;
  } catch (error) {
    console.log(error);
  }
};

  Creemos una función buscarPaisYVecinos y obtengamos la información de Colombia pasando col como alpha3code.

const buscarPaisYVecinos = async () => {
  const colombia = await buscarPais("col");

  console.log(colombia);
};

buscarPaisYVecinos();

  Ahora, si miras en tu consola, puedes ver un objeto con este aspecto:

Alt Text

  En el objeto, hay una propiedad de border que es una lista de alpha3codes para los países vecinos de Colombia.

  Ahora bien, si intentamos llegar a los países vecinos mediante:

  const vecinos = colombia.borders.map((border) => buscarPais(border));

  Entonces, vecinos serán un arreglo de objetos Promise.

  Cuando trabajamos con un arreglo de promesas, necesitamos usar Promise.all:

const buscarPaisYVecinos = async () => {
  const colombia = await buscarPais("col");

  const vecinos = await Promise.all(
    colombia.borders.map((border) => buscarPais(border))
  );

  console.log(vecinos);
};

buscarPaisYVecinos();

  En la consola, deberíamos poder ver la lista de objetos de países.

  Aquí está todo el código para la tarea 4 nuevamente para tu referencia:

const buscarPais = async (alpha3Code) => {
  try {
    const res = await fetch(
      `https://restcountries.eu/rest/v2/alpha/${alpha3Code}`
    );

    const data = await res.json();

    return data;
  } catch (error) {
    console.log(error);
  }
};

const buscarPaisYVecinos = async () => {
  const colombia = await buscarPais("col");

  const vecinos = await Promise.all(
    colombia.borders.map((border) => buscarPais(border))
  );

  console.log(vecinos);
};

buscarPaisYVecinos();

Conclusión

Alt Text

  Después de completar estas 4 tareas, puedes ver que Promise es útil cuando se trata de acciones asíncronas, o cosas que no suceden al mismo tiempo.

  Puedes ver esto en la práctica en uno de estos tutoriales, donde creamos una aplicación desde cero con React y Next.js:

_________ ? Acerca de Thu Nghiem_________

Traducido del artículo de Thu Nghiem - How to Learn JavaScript Promises and Async/Await in 20 Minutes