Saltar al contenido principal

Callbacks vs Promises

En JavaScript, cuando necesitamos manejar tareas asincrónicas (peticiones a APIs, timers, operaciones en disco, etc.), tenemos varias herramientas:

  1. Callbacks: el mecanismo más antiguo y básico.
  2. Promises: una evolución más robusta que permite escribir código más legible.

¿Qué es un Callback?

Un callback es una función que se pasa como argumento a otra función y se ejecuta cuando la tarea ha terminado.

function getData(callback: (data: string) => void) {
setTimeout(() => {
callback("Datos cargados");
}, 2000);
}

getData((data) => {
console.log(data); // "Datos cargados"
});

Flujo asincrónico con Callbacks

Escenario base para callbacks.

console.log("Inicio");

setTimeout(() => {
console.log("Callback ejecutado");
}, 2000);

console.log("Fin");
Inicio
Fin
Callback ejecutado

Callback Hell

Cuando se encadenan muchos callbacks, el código se vuelve difícil de leer y mantener:

loginUser("user", (user) => {
getUserOrders(user, (orders) => {
getOrderDetails(orders[0], (details) => {
console.log(details);
});
});
});

Este patrón dificulta:

  • Mantenimiento: pequeños cambios pueden romper toda la estructura.
  • Manejo de errores: debes capturar errores en cada nivel.
  • Lectura: el flujo lógico está invertido y fragmentado.

¿Qué es una Promise?

Una Promise es un objeto que representa un valor que estará disponible ahora, en el futuro o nunca. Puede estar en tres estados:

  1. Pending (pendiente)
  2. Fulfilled (resuelta correctamente)
  3. Rejected (rechazada)
const promise = new Promise<string>((resolve, reject) => {
setTimeout(() => {
resolve("Datos cargados");
}, 2000);
});

promise
.then((data) => console.log(data))
.catch((err) => console.error(err));

También puedes ejecutar tareas en paralelo o en serie con métodos como:

  • Promise.all([p1, p2, p3])
  • Promise.race([p1, p2])
  • Promise.allSettled([p1, p2])
  • Promise.any([p1, p2])

Flujo asincrónico con Promises

Ejemplo de código con Promises para el mismo escenario que antes:

console.log("Inicio");

Promise.resolve().then(() => {
console.log("Microtarea de Promise");
});

console.log("Fin");
Inicio
Fin
Microtarea de Promise

Encadenamiento de Promesas (Promises Chaining)

Las Promises eliminan el anidamiento profundo, o el Callback Hell.

loginUser("user")
.then((user) => getUserOrders(user))
.then((orders) => getOrderDetails(orders[0]))
.then((details) => console.log(details))
.catch((err) => console.error(err));

Diferencias principales

CaracterísticaCallbacksPromises
LegibilidadPuede degradarse con múltiples niveles (callback hell)Más lineal con .then y .catch
Manejo de erroresDebes manejar cada error manualmente en cada callbackcatch captura errores de toda la cadena
EncadenamientoDifícilNativo con .then
Estados intermediosNoPending, Fulfilled, Rejected

a

Ejemplo técnico

Supongamos que tenemos que:

  1. Autenticar al usuario.
  2. Cargar su perfil.
  3. Obtener notificaciones.

Usando solo callbacks llegaríamos al Callback Hell:

loginUser("user", (user) => {
loadProfile(user.id, (profile) => {
getNotifications(profile.id, (notifications) => {
console.log("Notificaciones:", notifications);
});
});
});

Pero, usando Promises, es mucho más claro y manejable.

loginUser("user")
.then(loadProfile)
.then(getNotifications)
.then((notifications) => console.log("Notificaciones:", notifications))
.catch((err) => console.error("Error:", err));

Aplicaciones

  1. Llamadas a APIs REST o GraphQL.
  2. Lectura de archivos en Node.js.
  3. Flujo de autenticación (login → cargar datos de usuario → cargar permisos).

Buenas prácticas al usar Promises

  • Siempre maneja los errores con .catch o try/catch.
  • Usa Promise.all si las tareas pueden ejecutarse en paralelo.
  • Evita mezclar callbacks y Promises innecesariamente.
  • Mantén las funciones pequeñas y de responsabilidad única.

Referencias