El JavaScript asíncrono es donde las entrevistas dejan de ser de definiciones y empiezan a ser de cómo corre el lenguaje de verdad. Vamos a construirlo desde el principio.
Por qué JavaScript necesita código asíncrono
JavaScript corre en un solo hilo: ejecuta una cosa a la vez, en una sola pila de llamadas (call stack).
Eso es un problema cuando algo tarda, como una petición a una API, un temporizador o leer un archivo. Si JavaScript simplemente esperara, toda la página se congelaría: ni clics, ni scroll, ni animaciones.
Entonces, en lugar de esperar, JavaScript le pasa el trabajo lento al entorno (el navegador o Node.js), sigue ejecutando otro código y regresa por el resultado después. Eso es la programación asíncrona.
El event loop
Esta es la parte que hace que todo lo demás tenga sentido.
Hay cuatro piezas:
- Call stack: donde corre tu código, una función a la vez.
- Web APIs (o APIs de Node): donde esperan los temporizadores, las peticiones de red y los eventos, fuera de la pila.
- Cola de tareas (macrotasks): callbacks de
setTimeout,setIntervaly eventos del DOM. - Cola de microtareas (microtasks): callbacks de promesas (
.then,await) yqueueMicrotask.
El event loop sigue una sola regla: cuando la pila está vacía, ejecuta todas las microtareas, luego una tarea, y repite.
La pregunta clásica de entrevista
¿Qué imprime esto?
console.log("1"); setTimeout(() => console.log("2"), 0); Promise.resolve().then(() => console.log("3")); console.log("4"); Respuesta: 1, 4, 3, 2.
-
1y4son síncronos, así que van primero. - El callback de la promesa se va a la cola de microtareas.
- El callback de
setTimeoutse va a la cola de tareas, aunque el tiempo sea0. - Cuando la pila queda vacía, primero corren las microtareas (
3) y luego la siguiente tarea (2).
Si puedes explicar esto paso a paso, ya respondiste la mitad de las preguntas de asincronía que te van a hacer.
Callbacks
Un callback es una función que se pasa a otra función para que la llame después.
function saludar(nombre, callback) { const mensaje = `Hola ${nombre}`; callback(mensaje); } saludar("Karen", (mensaje) => console.log(mensaje)); // "Hola Karen" Un error muy común al aprender es pasar el resultado de una función en lugar de la función: saludar(obtenerNombre()) en vez de saludar(obtenerNombre). Lo primero ya no es un callback.
Y uno asíncrono de verdad:
console.log("Antes"); setTimeout(() => { console.log("Dentro del callback"); }, 1000); console.log("Después"); // Antes, Después, Dentro del callback El problema: el callback hell
Cuando los pasos asíncronos dependen unos de otros, los callbacks se anidan:
obtenerUsuario(id, (usuario) => { obtenerPedidos(usuario.id, (pedidos) => { obtenerFactura(pedidos[0].id, (factura) => { enviarCorreo(factura, (resultado) => { // ...y manejo de errores en cada nivel }); }); }); }); Difícil de leer, de manejar errores y de cambiar. Las promesas se crearon para resolver eso.
Promesas
Una promesa es un objeto que representa un valor que va a estar disponible en el futuro, o un error que explica por qué no.
Tiene un estado:
| Estado | Qué significa |
|---|---|
pending | Todavía esperando |
fulfilled | Terminó bien, tiene un valor |
rejected | Falló, tiene una razón (normalmente un Error) |
Cuando una promesa se cumple o se rechaza, queda resuelta (settled) y ya nunca cambia. Por eso en este ejemplo el reject se ignora:
const promesa = new Promise((resolve, reject) => { resolve("listo"); reject(new Error("ignorado")); // la promesa ya estaba resuelta }); Crear una promesa
function esperar(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } Consumir una promesa
fetch("/api/usuario") .then((respuesta) => respuesta.json()) .then((usuario) => console.log(usuario.nombre)) .catch((error) => console.error("Algo falló:", error)) .finally(() => console.log("Terminó, haya salido bien o no")); Cada .then regresa una promesa nueva, así que puedes encadenar pasos en lugar de anidarlos. Un solo .catch al final maneja los errores de cualquier paso.
Correr varias promesas juntas
Aquí es donde suelen seguir las preguntas:
| Método | Se cumple cuando | Se rechaza cuando |
|---|---|---|
Promise.all | Todas salen bien | Cualquiera falla |
Promise.allSettled | Todas terminan, bien o mal | Nunca |
Promise.race | La primera termina | La primera termina con error |
Promise.any | La primera sale bien | Todas fallan |
async/await
async/await es sintaxis sobre las promesas que permite que el código asíncrono se lea como si fuera síncrono.
-
asynchace que una función siempre regrese una promesa. -
awaitpausa esa función hasta que una promesa se resuelve, sin bloquear el hilo principal.
async function mostrarUsuario(id) { try { const respuesta = await fetch(`/api/usuarios/${id}`); if (!respuesta.ok) throw new Error(`HTTP ${respuesta.status}`); const usuario = await respuesta.json(); console.log(`Hola ${usuario.nombre}`); } catch (error) { console.error("No se pudo cargar el usuario:", error); } } Los errores se manejan con un try/catch normal.
El detalle que demuestra seniority: secuencial vs. paralelo
// Secuencial: la segunda petición espera a la primera. Lento. const usuario = await obtenerUsuario(id); const ajustes = await obtenerAjustes(id); // Paralelo: las dos arrancan al mismo tiempo. Más rápido si no dependen una de otra. const [usuario, ajustes] = await Promise.all([obtenerUsuario(id), obtenerAjustes(id)]); Señalar esto en una entrevista, o en un code review, demuestra que piensas en el rendimiento y no solo en que funcione.
Respuestas para la entrevista
"¿Qué es el event loop?"
"JavaScript corre en un solo hilo con una sola pila de llamadas. El trabajo asíncrono, como temporizadores y peticiones de red, lo maneja el entorno, y sus callbacks se forman en colas. Cuando la pila está vacía, el event loop ejecuta todas las microtareas pendientes, como los callbacks de promesas, y luego la siguiente tarea, como un callback de setTimeout. Por eso una promesa resuelta corre antes que un setTimeout con tiempo cero."
"¿Callbacks, promesas o async/await?"
"Los callbacks son funciones que se pasan para ejecutarse después; se vuelven difíciles de manejar cuando se anidan. Las promesas representan un valor futuro con un estado, se pueden encadenar y centralizan el manejo de errores. async/await es sintaxis sobre las promesas que hace que el código asíncrono se lea de arriba a abajo, con try/catch para los errores."
Checklist rápida
- [ ] ¿Puedes explicar por qué JavaScript necesita código asíncrono si corre en un solo hilo?
- [ ] ¿Puedes dibujar la pila de llamadas, la cola de tareas y la de microtareas?
- [ ] ¿Puedes predecir la salida de
console.log,setTimeoutyPromise.thenjuntos? - [ ] ¿Puedes nombrar los tres estados de una promesa?
- [ ] ¿Sabes la diferencia entre
Promise.allyPromise.allSettled? - [ ] ¿Puedes convertir dos
awaitsecuenciales en paralelos?
Anterior en la serie: funciones y closures.
¿Qué bug asíncrono te ha costado más horas? Cuéntame en los comentarios 👇