DEV Community

Karen Molina
Karen Molina

Posted on

Prepárate para tu entrevista frontend: JavaScript asíncrono, event loop, promesas y async/await

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:

  1. Call stack: donde corre tu código, una función a la vez.
  2. Web APIs (o APIs de Node): donde esperan los temporizadores, las peticiones de red y los eventos, fuera de la pila.
  3. Cola de tareas (macrotasks): callbacks de setTimeout, setInterval y eventos del DOM.
  4. Cola de microtareas (microtasks): callbacks de promesas (.then, await) y queueMicrotask.

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");
Enter fullscreen mode Exit fullscreen mode

Respuesta: 1, 4, 3, 2.

  • 1 y 4 son síncronos, así que van primero.
  • El callback de la promesa se va a la cola de microtareas.
  • El callback de setTimeout se va a la cola de tareas, aunque el tiempo sea 0.
  • 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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
      });
    });
  });
});
Enter fullscreen mode Exit fullscreen mode

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
});
Enter fullscreen mode Exit fullscreen mode

Crear una promesa

function esperar(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}
Enter fullscreen mode Exit fullscreen mode

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"));
Enter fullscreen mode Exit fullscreen mode

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.

  • async hace que una función siempre regrese una promesa.
  • await pausa 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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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)]);
Enter fullscreen mode Exit fullscreen mode

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, setTimeout y Promise.then juntos?
  • [ ] ¿Puedes nombrar los tres estados de una promesa?
  • [ ] ¿Sabes la diferencia entre Promise.all y Promise.allSettled?
  • [ ] ¿Puedes convertir dos await secuenciales en paralelos?

Anterior en la serie: funciones y closures.

¿Qué bug asíncrono te ha costado más horas? Cuéntame en los comentarios 👇

Top comments (0)