Una función callback es una función que le pasas a otra función
En JavaScript las funciones son valores. Puedes guardarlas en variables, meterlas en arrays y, lo que más nos interesa aquí, pasarlas como argumentos. Cuando le pasas una función a otra para que esta la ejecute más adelante, esa función que pasaste se llama callback.
greet no sabe ni le importa qué hace formatter. Simplemente lo invoca pasándole un nombre y usa el resultado. Tú eliges el comportamiento pasando distintos callbacks. Esa flexibilidad es justamente la razón de ser de los callbacks.
Callbacks síncronos: se ejecutan al instante
No todos los callbacks son asíncronos. De hecho, muchos métodos de array que ya usas a diario funcionan con callbacks, y los ejecutan de forma síncrona, antes de que la llamada externa termine:
map, filter y reduce reciben una función callback y la invocan una vez por cada elemento, en ese mismo momento. Cuando map termina de ejecutarse, todas las llamadas al callback ya ocurrieron. No queda nada pendiente para después.
Esto es simplemente el patrón clásico de función de orden superior: "aquí tienes el trabajo, aquí tienes cómo hacerlo, devuélveme el resultado". El event loop no entra en juego.
Los callbacks asíncronos en JavaScript se ejecutan después
Cuando la gente habla de "callbacks" en JavaScript, normalmente se refieren a los asíncronos. Le pasas una función a alguna API que tarda en responder —un temporizador, una petición de red, la lectura de un archivo— y esa API llama a tu función cuando el trabajo termina.
Orden de salida: antes, después y, un segundo después, timer disparado. setTimeout no pausa tu programa. Le entrega el callback al runtime, retorna al instante y el resto del script sigue su curso. Un segundo más tarde, el event loop toma ese callback y lo ejecuta.
Ese patrón de "retorno ya, te llamo después" es el modelo mental detrás de cualquier API con callback asíncrono en JavaScript, desde addEventListener hasta las viejas APIs de archivos de Node.js.
El patrón error-first callback (Node.js)
Antes de que existieran las promesas, Node.js estandarizó una forma concreta de callback: el primer argumento es un error (o null) y el resto son el resultado real. Todavía te lo vas a cruzar en código antiguo y en algunas librerías.
Quien recibe la llamada revisa err primero y corta de raíz si tiene algo. Solo entonces confía en el resultado. Es una convención —el lenguaje no la obliga—, pero en cuanto veas la firma (err, result) => ..., la vas a reconocer en todas partes.
Callback hell en JavaScript
El lío empieza cuando un paso asíncrono depende del resultado del anterior. Cada callback tiene que ir anidado dentro del anterior y acabas armando una escalera infinita de indentación:
Esto es lo que se conoce como la famosa "pirámide de la perdición" o callback hell. Hay varias cosas que lo hacen insoportable:
- El flujo del código zigzaguea en lugar de leerse de arriba hacia abajo.
- En cada nivel se repite el mismo
if (err) return ...una y otra vez. - Si un callback lanza una excepción, esta no se propaga a los de fuera: toca manejar los errores capa por capa.
- Cualquier refactor implica reindentar el bloque entero.
Puedes aplanarlo un poco extrayendo funciones con nombre, pero el problema de fondo —que componer código asíncrono con callbacks puros es torpe— sigue ahí. Justo ese es el problema que vinieron a resolver las promesas.
Dos detalles que conviene tener presentes
No invoques el callback sin querer. Cuando pasas un callback, pasas la función en sí, no el resultado de ejecutarla.
Ojo con this. Si tu callback es una función tradicional que usa this, el valor de this dependerá de cómo se invoque la función, no de dónde la hayas definido. Las arrow functions te ahorran este dolor de cabeza, porque heredan el this del contexto en el que se crearon:
Las arrow functions son la opción por defecto para callbacks en línea justamente por eso.
Callback vs promesa en JavaScript
Los callbacks siguen apareciendo en APIs síncronas (map, forEach, sort), en listeners de eventos (element.addEventListener("click", ...)) y en hooks de bajo nivel del runtime. Pero para tareas asíncronas que devuelven un único resultado, el ecosistema se ha pasado casi por completo a las promesas.
Comparación rápida:
- Callbacks — directos y mínimos, pero se componen fatal. El manejo de errores es manual en cada paso.
- Promesas — un valor que representa un resultado futuro. Se encadenan con
.then(), los errores se gestionan una sola vez con.catch()y la pirámide se aplana.
Aun así, conviene entender bien los callbacks: son la base sobre la que están construidas las promesas y están por todas partes en código orientado a eventos. Lo que casi no vas a hacer es escribir nuevas APIs asíncronas con callbacks a pelo.
Siguiente: promesas
Las promesas cogen la idea de "haz esto cuando aquello esté listo" y la envuelven en un objeto que puedes pasar, encadenar y componer. De eso va la siguiente página, que además es el puente hacia async/await, la forma en la que el JavaScript moderno resuelve casi todo el trabajo asíncrono.
Preguntas frecuentes
¿Qué es una función callback en JavaScript?
Un callback es una función que le pasas como argumento a otra función para que esta la ejecute más adelante. Por ejemplo, setTimeout(() => console.log('hola'), 1000) le pasa una arrow function como callback: setTimeout la guarda y la llama cuando se cumple el temporizador. Los callbacks fueron la forma original que tenía JavaScript de decir 'haz esto cuando aquello esté listo'.
¿Cuál es la diferencia entre callbacks síncronos y asíncronos?
Un callback síncrono se ejecuta al momento, dentro de la misma llamada que lo recibió. Por ejemplo, [1, 2, 3].map(x => x * 2) invoca el callback tres veces antes de que map devuelva nada. Un callback asíncrono, en cambio, se guarda y se ejecuta más tarde, cuando ocurre algún evento: así funcionan setTimeout, fs.readFile y los listeners del DOM. Los asíncronos no bloquean el resto del código.
¿Qué es el callback hell y cómo se evita?
El callback hell es esa forma de pirámide que aparece cuando varios callbacks asíncronos dependen unos de otros y acaban anidados varios niveles. El flujo de control y el manejo de errores se vuelven un dolor de cabeza. La solución es usar promesas con cadenas de .then(), o mejor aún, async/await: ambas aplanan la pirámide y devuelven legibilidad al código.