JavaScript Asíncrono

JavaScript Asíncrono

JavaScript ejecuta una sola cosa a la vez, pero nunca se congela mientras espera. El event loop, los callbacks, las promises y async/await son la maquinaria que hace que esto sea posible.

intermediate16 min readUpdated 15 sept 2026
js
// user.js
async function loadUser(id) {
  const res = await fetch(`/api/users/${id}`);
  if (!res.ok) throw new Error("User not found");
  return res.json();
}

const user = await loadUser(42);
console.log(user.name);
Modelo de ejecución
Event loop de un solo hilo
Primera solución
Callbacks
Solución moderna
Promises + async/await
Estados de una Promise
pending, fulfilled, rejected
Ejecución en paralelo
Promise.all / allSettled
Palabras clave
async / await

Practico

Observa el event loop

El orden de los mensajes muestra cómo el event loop programa el trabajo.

Observa el event loop

El orden de los mensajes muestra cómo el event loop programa el trabajo.

Por que importa

Por qué importa lo asíncrono

Nunca bloquea la página

El trabajo pesado ocurre fuera del hilo principal mientras la UI permanece receptiva, evitando que los usuarios se queden mirando una pestaña congelada.

Se lee como código síncrono

async/await te permite escribir lógica asíncrona de arriba hacia abajo, sin anidar callbacks ni encadenar bloques then.

Diseñado para la red

Cada fetch, temporizador, lectura de archivo y llamada a base de datos en el JavaScript moderno es asíncrono; por lo tanto, esto es fundamental, no opcional.

La imagen completa

Las tres piezas del rompecabezas

El JavaScript asíncrono no es una sola funcionalidad, sino tres capas superpuestas: el event loop, las promises y la sintaxis que se lee como código síncrono.

El event loop

Programación

Un call stack, una cola de tareas (task queue) y una cola de microtareas (microtask queue) deciden el orden en que tu código se ejecuta realmente.

Promises

Representación

Un objeto que representa un valor futuro, con una forma limpia de encadenar trabajo y manejar fallos.

async / await

Sintaxis

Una capa ligera sobre las promises que hace que el código asíncrono se vea y se lea como código síncrono ordinario.

Asincronía de un vistazo

Las herramientas de tu kit asíncrono

El event loop

Ejecuta el trabajo en cola una vez que el código síncrono actual ha terminado.

Callbacks

El patrón original: pasar una función para que se ejecute cuando el trabajo se complete.

Promises

Objetos con tres estados que permiten encadenar manejadores de éxito y fallo.

Combinadores

all, allSettled, race y any coordinan múltiples promises a la vez.

async / await

Pausa una función en el await sin bloquear el resto del programa.

Manejo de errores

catch y try/catch convierten las promises rechazadas en errores normales y manejables.

Una breve historia

De los callbacks a async/await

  1. 2005

    AJAX populariza los callbacks

    Gmail y Google Maps muestran al mundo que las páginas pueden actualizarse sin recargarse.

    05
  2. 2009

    Node.js adopta lo asíncrono

    El JavaScript del lado del servidor convierte la E/S no bloqueante en la forma predeterminada de construir backends.

    09
  3. 2015

    Promises en ES6

    El objeto Promise se estandariza, otorgando al código asíncrono un contrato compartido.

    15
  4. 2017

    async / await

    ES2017 añade la sintaxis que hace que el código asíncrono se lea como código síncrono.

    17
  5. Hoy

    Asincronía en todas partes

    Fetch, streams, workers y la mayoría de las API modernas se basan en promises por defecto.

    Hoy

La guia completa

JavaScript Asíncrono: Todo lo que necesitas saber

Por qué existe el JavaScript asíncrono

Las computadoras realizan muchas tareas lentamente: leer archivos, hacer consultas a bases de datos o esperar a que un servidor responda. Si JavaScript se detuviera a esperar cada una de estas acciones, la página se congelaría y los usuarios se irían. La programación asíncrona es la forma en que el lenguaje sigue funcionando mientras las tareas lentas se ejecutan en otro lugar.

El modelo mental es el de un restaurante. Un único camarero toma tu pedido, lo pasa a la cocina e inmediatamente atiende a otra mesa. Cuando tu comida está lista, la cocina toca una campana y el camarero la trae a la mesa. El camarero nunca se queda parado en la cocina mirando cómo se cocina la comida. JavaScript es ese camarero, y el event loop es la campana.

El call stack y el event loop

JavaScript tiene un único call stack. Cada llamada a una función añade un frame; cuando esta retorna, el frame se elimina. Debido a que solo hay un stack, solo puede ejecutarse una cosa a la vez.

Las API asíncronas son proporcionadas por el entorno host, no por el lenguaje. Cuando llamas a setTimeout, fetch o a una lectura de archivo, el navegador o Node.js se encarga de la espera. Cuando el trabajo termina, su callback se coloca en una cola. El event loop verifica constantemente: ¿está el call stack vacío? Si es así, toma el siguiente callback de la cola y lo ejecuta.

// order.js
console.log("start");

setTimeout(() => console.log("timeout"), 0);

Promise.resolve().then(() => console.log("promise"));

console.log("end");

// start, end, promise, timeout

Hay dos detalles importantes. Primero, el código síncrono siempre termina antes de que se ejecute cualquier callback. Segundo, existen dos colas: las microtasks (callbacks de promesas) se ejecutan inmediatamente después de la tarea actual, antes que las tasks (timers, I/O). Es por eso que promise se registra en el log antes que timeout.

Callbacks

La forma original de manejar resultados asíncronos es mediante un callback: una función que pasas como argumento para que sea ejecutada más tarde.

// callback.js
function getUser(id, callback) {
  setTimeout(() => {
    callback({ id, name: "Ada" });
  }, 300);
}

getUser(1, (user) => {
  console.log(user.name);
});

Los callbacks funcionan, pero su composición es deficiente. Cuando cada paso depende del anterior, terminas anidando funciones, y el resultado es el callback hell: código que avanza diagonalmente a través de la pantalla y hace que el manejo de errores sea tedioso.

// hell.js
getUser(1, (user) => {
  getPosts(user.id, (posts) => {
    getComments(posts[0].id, (comments) => {
      // three levels deep and counting
    });
  });
});

Promises

Una Promise es un objeto que representa un valor que puede que aún no exista. Se encuentra en uno de estos tres estados:

  • pending — el trabajo aún se está ejecutando.
  • fulfilled — terminó exitosamente con un valor.
  • rejected — falló por alguna razón.

Una vez resuelta, una promise nunca cambia de estado. Puedes adjuntar manejadores con then, catch y finally.

// promise.js
function getUser(id) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (id > 0) resolve({ id, name: "Ada" });
      else reject(new Error("Invalid id"));
    }, 300);
  });
}

getUser(1)
  .then((user) => console.log(user.name))
  .catch((error) => console.error(error.message))
  .finally(() => console.log("done"));

El encadenamiento es la ventaja clave. Cada then devuelve una nueva promise, por lo que en lugar de anidar, puedes aplanar la secuencia y manejar cada error en un único catch al final.

// chain.js
getUser(1)
  .then((user) => getPosts(user.id))
  .then((posts) => getComments(posts[0].id))
  .then((comments) => console.log(comments))
  .catch((error) => console.error("Something failed:", error));

Combinando promesas

Cuando intervienen varias operaciones asíncronas, el constructor Promise proporciona combinadores.

// combine.js
const [user, posts, settings] = await Promise.all([
  getUser(1),
  getPosts(1),
  getSettings(),
]);

const results = await Promise.allSettled([getUser(1), getUser(-1)]);

const fastest = await Promise.race([getFromCache(), getFromNetwork()]);

const firstSuccess = await Promise.any([mirrorA(), mirrorB()]);

Promise.all ejecuta todo en paralelo y falla si cualquier promesa es rechazada. Promise.allSettled espera a que todas terminen y reporta cada resultado. Promise.race se resuelve con la primera promesa que se liquide, y Promise.any se resuelve con la primera que tenga éxito. Elegir la opción correcta cambia drásticamente el comportamiento de los errores.

async y await

async y await son sintaxis construidas sobre las promesas. Marcar una función como async hace que siempre devuelva una promesa. Dentro de ella, await pausa la función hasta que una promesa se resuelve, y luego continúa con el valor resuelto, sin bloquear el resto del programa.

// await.js
async function loadDashboard() {
  try {
    const user = await getUser(1);
    const posts = await getPosts(user.id);
    return { user, posts };
  } catch (error) {
    console.error("Failed to load dashboard", error);
    throw error;
  }
}

Esto se lee como código síncrono, que es precisamente el objetivo. Los errores se gestionan con los bloques try/catch habituales, y finally sigue ejecutándose. La función devuelve una promesa, por lo que quienes la llamen pueden hacerle un await o encadenar .then como de costumbre.

Secuencial vs paralelo

Un error sutil pero costoso es esperar tareas independientes una por una. Cada await espera a la anterior, por lo que el tiempo total es la suma de todas. Cuando las tareas no dependen entre sí, inicíalas simultáneamente.

// parallel.js
// Sequential — slow, ~600ms
const a = await slowTask("a");
const b = await slowTask("b");

// Parallel — fast, ~300ms
const [x, y] = await Promise.all([slowTask("x"), slowTask("y")]);

Usa await secuenciales solo cuando un paso posterior realmente necesite el resultado de uno anterior, como obtener un usuario antes de obtener las publicaciones de dicho usuario.

Manejo de errores en código async

Las promesas rechazadas que nadie gestiona se convierten en unhandled rejections, lo que puede provocar la caída de procesos de Node.js y llenar la consola en los navegadores.

// errors.js
try {
  const res = await fetch("/api/data");
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  const data = await res.json();
} catch (error) {
  showMessage("Could not load data. Please try again.");
} finally {
  hideSpinner();
}

Prioriza el uso de try/catch alrededor de las llamadas con await, lanza objetos Error (no strings) y asegúrate siempre de gestionar el error o relanzarlo deliberadamente. La guía de Manejo de Errores profundiza en estrategias y mensajes orientados al usuario.

Errores comunes

  • Olvidar await. La función devuelve una promesa, no el valor, por lo que la siguiente línea se ejecuta demasiado pronto.
  • forEach con callbacks asíncronos. forEach ignora las promesas devueltas; utiliza for...of o Promise.all con map.
  • Usar await en un bucle cuando se podría hacer en paralelo. Funciona, pero es innecesariamente lento.
  • No retornar dentro de then. La falta de un return rompe la cadena y hace que se pierda el valor.
  • Asumir el orden. Solo await y el orden de las microtareas garantizan la secuencia; las tareas concurrentes terminan cuando terminan.
  • Silenciar errores con un catch vacío, ocultando el bug que necesitabas ver.

Mejores prácticas

  • Prefiere async/await en lugar de cadenas largas de .then para mejorar la legibilidad.
  • Ejecuta tareas independientes en paralelo con Promise.all o Promise.allSettled.
  • Maneja siempre los rechazos, aunque sea solo para registrarlos y volver a lanzarlos.
  • Mantén las funciones async pequeñas y asígnales una sola responsabilidad.
  • Usa Promise.allSettled cuando los resultados parciales sean aceptables.
  • Cancela el trabajo que ya no necesites con un AbortController.
  • Muestra estados de carga y de error para que los usuarios entiendan qué está sucediendo.

Próximos pasos

Ya tienes el kit de herramientas asíncronas completo. Ponlo en práctica con la Fetch API, donde las promesas y await impulsan cada solicitud de red, y refuérzalo con los patrones de Manejo de Errores.

Encadenamiento vs awaiting

async/await elimina los callbacks anidados y hace que la secuencia sea obvia.

Preferido
async function load() {
  const user = await getUser(1);
  const posts = await getPosts(user.id);
  return posts;
}
Evitar
function load() {
  return getUser(1).then((user) => {
    return getPosts(user.id).then((posts) => {
      return posts;
    });
  });
}

Trabajo independiente

Si las solicitudes no dependen entre sí, inícialas juntas con Promise.all.

Preferido
const [user, posts] = await Promise.all([
  getUser(1),
  getPosts(1),
]);
Evitar
const user = await getUser(1);
const posts = await getPosts(1);
// runs one after the other

Preguntas frecuentes

Preguntas frecuentes

Keep learning

Related topics from the roadmap.

$ comienza a aprender

Listo para aprender Async JavaScript?

Nuestro tutorial interactivo te guia a traves de Async JavaScript paso a paso — con quizzes y codigo real que puedes ejecutar en el navegador.