Skip to content

Promesas y async/await en JavaScript ​

1. ¿Qué es la programación asíncrona? ​

En JavaScript, una operación asíncrona es aquella cuyo resultado puede tardar en estar disponible, por lo que el programa puede continuar ejecutando otras tareas mientras espera.

Por ejemplo, imagina que una aplicación necesita consultar una API para obtener información de un usuario. La respuesta podría tardar unos milisegundos o varios segundos.

Algunas operaciones asíncronas comunes son:

  • Consultar una API mediante fetch().
  • Leer archivos en entornos como Node.js.
  • Consultar una base de datos desde el backend.
  • Esperar un temporizador con setTimeout().

Ejemplo: una operación que tarda ​

js
console.log("Inicio");

setTimeout(() => {
  console.log("Operación terminada");
}, 2000);

console.log("Fin");

Resultado:

text
Inicio
Fin
Operación terminada

Aunque setTimeout() espera dos segundos antes de ejecutar su función, JavaScript no se queda detenido durante ese tiempo. Continúa con las siguientes instrucciones.

Importante: setTimeout() establece un tiempo mínimo de espera, no garantiza que la función se ejecute exactamente en ese instante.

2. ¿Qué es una promesa? ​

Una Promise (promesa) es un objeto que representa el resultado futuro de una operación asíncrona.

Ese resultado puede ser un valor obtenido correctamente o un error.

Una promesa tiene tres estados:

EstadoSignificado
pendingLa operación sigue pendiente.
fulfilledLa operación terminó correctamente.
rejectedLa operación terminó con un error.

Una vez que una promesa se cumple o se rechaza, queda establecida en ese estado.

Crear una promesa ​

js
const promesa = new Promise((resolve, reject) => {
  const operacionExitosa = true;

  if (operacionExitosa) {
    resolve("Operación completada");
  } else {
    reject(new Error("La operación falló"));
  }
});

En este ejemplo:

  • new Promise() crea una promesa.
  • resolve() indica que la operación terminó correctamente y proporciona su resultado.
  • reject() indica que la operación falló y proporciona la razón del rechazo.

El código dentro del constructor de Promise se ejecuta inmediatamente. La promesa representa el resultado, pero no hace que ese código se ejecute automáticamente en segundo plano.

3. ¿Cómo se utiliza .then()? ​

El método .then() permite definir qué hacer cuando una promesa se cumple correctamente.

js
promesa.then((resultado) => {
  console.log(resultado);
});

Resultado:

text
Operación completada

El parámetro resultado recibe el valor que se pasó a resolve().

¿Y si la promesa falla? ​

Se puede utilizar .catch() para manejar un rechazo:

js
const promesa = new Promise((resolve, reject) => {
  reject(new Error("No se pudo completar la operación"));
});

promesa
  .then((resultado) => {
    console.log(resultado);
  })
  .catch((error) => {
    console.error(error.message);
  });

Resultado:

text
No se pudo completar la operación

Como la promesa fue rechazada, no se ejecuta el callback de .then() y el error llega a .catch().

Encadenar promesas ​

Los métodos .then() y .catch() devuelven nuevas promesas. Esto permite encadenar operaciones:

js
Promise.resolve(5)
  .then((numero) => {
    return numero * 2;
  })
  .then((resultado) => {
    console.log(resultado);
  })
  .catch((error) => {
    console.error(error);
  });

Resultado:

text
10

Cada .then() recibe el resultado producido por el anterior. Si un callback devuelve un valor, ese valor pasa a la siguiente etapa; si devuelve una promesa, la cadena espera su resultado.

4. ¿Qué es async/await? ​

async/await es una sintaxis que facilita trabajar con promesas. Permite escribir código asíncrono de una forma que se parece a una secuencia normal de instrucciones.

La palabra clave async ​

Cuando colocas async delante de una función, esta siempre devuelve una promesa.

js
async function obtenerNumero() {
  return 10;
}

obtenerNumero().then((numero) => {
  console.log(numero);
});

Resultado:

text
10

Aunque la función devuelve directamente 10, JavaScript lo entrega a través de una promesa resuelta.

La palabra clave await ​

await permite esperar el resultado de una promesa dentro de una función async.

js
function esperarNumero() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(10);
    }, 2000);
  });
}

async function ejecutar() {
  console.log("Inicio");

  const numero = await esperarNumero();

  console.log(numero);
  console.log("Fin");
}

ejecutar();

Resultado:

text
Inicio
10
Fin

El programa espera el resultado de esperarNumero() antes de ejecutar la siguiente instrucción dentro de ejecutar().

Eso no significa que todo JavaScript quede bloqueado. Mientras la función está esperando, el entorno puede continuar atendiendo otras tareas.

En código JavaScript moderno, await también puede utilizarse en el nivel superior de módulos compatibles con top-level await.

5. Diferencia entre .then() y async/await ​

Ambas formas permiten trabajar con promesas. La diferencia principal es cómo se organiza el código.

Con .then():

js
function obtenerUsuario() {
  return fetch("https://jsonplaceholder.typicode.com/users/1")
    .then((respuesta) => {
      if (!respuesta.ok) {
        throw new Error("Error al obtener el usuario");
      }

      return respuesta.json();
    })
    .then((usuario) => {
      console.log(usuario);
    })
    .catch((error) => {
      console.error(error.message);
    });
}

Con async/await:

js
async function obtenerUsuario() {
  try {
    const respuesta = await fetch(
      "https://jsonplaceholder.typicode.com/users/1"
    );

    if (!respuesta.ok) {
      throw new Error("Error al obtener el usuario");
    }

    const usuario = await respuesta.json();

    console.log(usuario);
  } catch (error) {
    console.error(error.message);
  }
}

Ambos ejemplos realizan una operación similar. async/await suele resultar más fácil de leer cuando hay varios pasos que deben ejecutarse en orden.

6. ¿Cómo se relacionan con las API? ​

Cuando se utiliza fetch() para consultar una API, se obtiene una promesa que se resolverá con un objeto Response cuando llegue la respuesta HTTP.

Después, métodos como respuesta.json() también devuelven una promesa, porque leer y convertir el cuerpo de la respuesta es otra operación que debe completarse.

js
async function obtenerProductos() {
  try {
    const respuesta = await fetch(
      "https://jsonplaceholder.typicode.com/posts"
    );

    if (!respuesta.ok) {
      throw new Error(`Error HTTP: ${respuesta.status}`);
    }

    const productos = await respuesta.json();

    console.log(productos);
  } catch (error) {
    console.error("No se pudieron obtener los datos:", error.message);
  }
}

obtenerProductos();

Este ejemplo consulta una API pública de publicaciones para demostrar el proceso. Los datos obtenidos no son productos reales; la variable productos solo se utiliza como nombre ilustrativo.

¿Qué ocurre paso a paso? ​

  1. fetch() inicia la petición HTTP y devuelve una promesa.
  2. await espera la respuesta HTTP.
  3. respuesta.ok permite comprobar si el estado HTTP está entre 200 y 299.
  4. respuesta.json() devuelve otra promesa para convertir el cuerpo de la respuesta en datos JavaScript.
  5. El segundo await espera esos datos.
  6. catch permite manejar errores de red, errores de conversión y errores lanzados explícitamente.

Importante: fetch() no rechaza automáticamente su promesa por recibir estados HTTP como 404 o 500. Por eso conviene comprobar respuesta.ok.

7. Manejo de errores con try/catch ​

Con async/await, se puede utilizar try/catch para manejar errores de una forma familiar:

js
async function cargarDatos() {
  try {
    const respuesta = await fetch("/api/usuarios");

    if (!respuesta.ok) {
      throw new Error("No fue posible obtener los usuarios");
    }

    const usuarios = await respuesta.json();

    return usuarios;
  } catch (error) {
    console.error(error.message);
    return [];
  }
}

En este ejemplo:

  • try contiene las instrucciones que podrían fallar.
  • throw genera un error cuando la respuesta HTTP no es exitosa.
  • catch recibe el error y permite decidir cómo manejarlo.
  • return [] devuelve un arreglo vacío como alternativa si ocurre un error.

En una aplicación real, devolver un arreglo vacío puede ser útil en ciertos casos, pero también puede ocultar que hubo un fallo. La decisión depende de lo que necesite hacer la aplicación.

8. Ejecutar operaciones en paralelo con Promise.all() ​

Por defecto, usar await varias veces seguidas hace que cada operación espere a la anterior.

js
const usuario = await obtenerUsuario();
const publicaciones = await obtenerPublicaciones();

Esto es adecuado cuando la segunda operación depende de la primera. Pero si ambas son independientes, pueden iniciarse juntas:

js
const [usuario, publicaciones] = await Promise.all([
  obtenerUsuario(),
  obtenerPublicaciones()
]);

Promise.all() espera a que todas las promesas se cumplan y devuelve sus resultados en el mismo orden en que fueron proporcionadas.

Si alguna promesa se rechaza, la promesa devuelta por Promise.all() se rechaza también. Eso no cancela automáticamente las demás operaciones.

Regla práctica:

  • Usa await secuencial cuando una operación dependa del resultado de otra.
  • Usa Promise.all() cuando varias operaciones independientes puedan ejecutarse al mismo tiempo.

9. Errores comunes ​

Olvidar await ​

js
async function obtenerDatos() {
  const respuesta = fetch("/api/usuarios");

  console.log(respuesta);
}

Aquí, respuesta contiene una promesa, no el objeto Response que se necesita para leer los datos.

Forma correcta:

js
async function obtenerDatos() {
  const respuesta = await fetch("/api/usuarios");
  console.log(respuesta.status);
}

Usar await dentro de una función que no es async ​

js
function cargarDatos() {
  const respuesta = await fetch("/api/usuarios");
}

Esto produce un error de sintaxis en una función normal.

Forma correcta:

js
async function cargarDatos() {
  const respuesta = await fetch("/api/usuarios");
}

Pensar que async hace que todo se ejecute en paralelo ​

async no convierte automáticamente todas las instrucciones en operaciones paralelas. Si necesitas ejecutar varias tareas independientes simultáneamente, puedes iniciar sus promesas y usar Promise.all().

Olvidar manejar los errores ​

Una promesa rechazada que no se maneja puede provocar errores no controlados. Utiliza .catch() o try/catch según la forma en que trabajes con las promesas.

10. Resumen ​

Concepto¿Para qué sirve?
AsincroníaPermite gestionar operaciones que tardan sin bloquear toda la ejecución de JavaScript.
PromiseRepresenta el resultado futuro de una operación.
resolve()Cumple una promesa con un resultado.
reject()Rechaza una promesa con una razón de error.
.then()Procesa el resultado de una promesa cumplida.
.catch()Maneja un rechazo o un error propagado por la cadena.
asyncDeclara una función que devuelve una promesa.
awaitEspera el resultado de una promesa dentro de un contexto compatible.
try/catchPermite manejar errores al utilizar async/await.
Promise.all()Espera varias promesas en paralelo y reúne sus resultados.

La idea fundamental es que las promesas representan resultados futuros y async/await facilita trabajar con ellas. Son especialmente útiles al consultar API, acceder a datos desde un backend o realizar cualquier operación que no termina inmediatamente.