Skip to content

L55 - Callbacks

Un callback es una función que se pasa como argumento a otra función para que sea ejecutada más tarde, ya sea de forma síncrona (inmediata) o asíncrona (cuando ocurra un evento o se complete una operación).

JavaScript trata las funciones como ciudadanos de primera clase: pueden asignarse a variables, pasarse como argumentos y retornarse desde otras funciones.

Los callbacks síncronos se ejecutan inmediatamente dentro de la función que los recibe:

function procesarArray(arr, callback) {
for (let i = 0; i < arr.length; i++) {
arr[i] = callback(arr[i]);
}
return arr;
}
const numeros = [1, 2, 3];
const duplicados = procesarArray(numeros, n => n * 2);
console.log(duplicados); // [2, 4, 6]
// forEach
[1, 2, 3].forEach(n => console.log(n));
// map
const cuadrados = [1, 2, 3].map(n => n ** 2);
// filter
const pares = [1, 2, 3, 4].filter(n => n % 2 === 0);
// sort
[3, 1, 2].sort((a, b) => a - b);
// reduce
const suma = [1, 2, 3].reduce((acc, n) => acc + n, 0);

Se ejecutan más tarde, cuando una operación asíncrona termina:

// setTimeout
setTimeout(() => {
console.log('Esto se ejecuta después de 1 segundo');
}, 1000);
// Event listeners
btn.addEventListener('click', () => {
console.log('El usuario hizo clic');
});
// readFile (Node.js)
fs.readFile('datos.txt', 'utf8', (err, data) => {
if (err) console.error(err);
else console.log(data);
});

⚠️ Callback Hell (La pirámide de la perdición)

Section titled “⚠️ Callback Hell (La pirámide de la perdición)”

Cuando se anidan muchos callbacks, el código se vuelve ilegible:

obtenerUsuario(id, (err, usuario) => {
if (err) {
console.error(err);
} else {
obtenerPedidos(usuario.id, (err, pedidos) => {
if (err) {
console.error(err);
} else {
obtenerDetalles(pedidos[0].id, (err, detalle) => {
if (err) {
console.error(err);
} else {
console.log('Detalle:', detalle);
}
});
}
});
}
});
  1. Legibilidad: el código crece hacia la derecha y es difícil de seguir.
  2. Manejo de errores repetitivo: hay que verificar err en cada nivel.
  3. Dificultad para mantener: añadir un paso extra implica reestructurar todo.
  4. Dificultad para depurar: el stack trace se pierde entre callbacks.

Encadenar .then() en lugar de anidar:

obtenerUsuario(id)
.then(usuario => obtenerPedidos(usuario.id))
.then(pedidos => obtenerDetalles(pedidos[0].id))
.then(detalle => console.log(detalle))
.catch(err => console.error(err));

La forma más legible:

try {
const usuario = await obtenerUsuario(id);
const pedidos = await obtenerPedidos(usuario.id);
const detalle = await obtenerDetalles(pedidos[0].id);
console.log(detalle);
} catch (err) {
console.error(err);
}

Extraer funciones nombradas en lugar de usar anónimas:

function onUsuario(err, usuario) {
if (err) return console.error(err);
obtenerPedidos(usuario.id, onPedidos);
}
function onPedidos(err, pedidos) {
if (err) return console.error(err);
obtenerDetalles(pedidos[0].id, onDetalle);
}
function onDetalle(err, detalle) {
if (err) return console.error(err);
console.log(detalle);
}
obtenerUsuario(id, onUsuario);

Callback con manejo de errores (Node style)

Section titled “Callback con manejo de errores (Node style)”
function dividir(a, b, callback) {
if (b === 0) {
callback(new Error('No se puede dividir por cero'));
} else {
callback(null, a / b);
}
}
dividir(10, 2, (err, resultado) => {
if (err) return console.error(err);
console.log(resultado); // 5
});
function fetchData(url, callback) {
// ... operación ...
if (typeof callback === 'function') {
callback(null, datos);
}
}

🎯 Regla de oro: usa callbacks para APIs que los requieran (eventos, middleware), pero prefiere Promesas o async/await para tu propio código asíncrono.