JavaScript es asíncrono: cuando pide algo que tarda —datos de una API, un archivo, un temporizador—, no se queda esperando; sigue ejecutando el resto y atiende el resultado cuando llega. Eso evita que la página se congele, pero obliga a manejar la espera. La herramienta moderna para eso es la promesa (Promise): un objeto que representa un valor que todavía no está pero llegará, y que puede terminar bien (resuelta) o mal (rechazada). Sobre las promesas está async/await, una sintaxis que hace que el código asíncrono se lea como si fuera secuencial: await espera a que una promesa se resuelva sin bloquear todo. Los errores se manejan con try/catch, igual que el código normal. Y para varias tareas a la vez sin esperar una por una está Promise.all. Dominar esto es imprescindible, porque consumir cualquier API es asíncrono.
Ver índice de contenidos
Por qué JavaScript no espera
Esta es la idea que hay que entender antes que cualquier sintaxis, y la que más traba a quien viene de otros lenguajes. JavaScript ejecuta una sola cosa a la vez, pero cuando una tarea tarda —pedir datos por red, leer un archivo— no se queda parado esperándola: la deja en marcha, sigue con lo que viene, y vuelve a atender el resultado cuando está listo.
Por qué es así y no de otra forma
Si JavaScript esperara cada respuesta de red, la página entera se congelaría mientras tanto: no responderían los botones, no se movería nada. Al no esperar, la interfaz sigue viva mientras los datos llegan de fondo. El precio de esa fluidez es que el resultado de una tarea lenta llega después, en otro momento, y hay que decirle al programa qué hacer cuando eso pase. Todo —callbacks, promesas, async/await— son formas de responder a esa única pregunta: ¿qué hago cuando el resultado esté listo?
Las promesas
Una promesa es un objeto que representa un resultado que todavía no está pero va a llegar. Como un ticket de guardarropa: no tenés el abrigo ahora, pero tenés algo que lo vas a canjear. Una promesa está en uno de tres estados: pendiente (la tarea sigue), resuelta (terminó bien, con un valor) o rechazada (falló, con un error).
Las promesas nacieron para resolver el viejo «infierno de callbacks»: antes, para hacer algo tras una tarea lenta se pasaba una función, y al encadenar varias tareas dependientes esas funciones quedaban anidadas en una pirámide ilegible. Las promesas se encadenan de forma plana en su lugar. Pero hoy casi no se escriben a mano: se usan a través de async/await.
async/await: lo asíncrono legible
async/await es la sintaxis moderna, construida sobre las promesas, que hace que el código asíncrono se lea de arriba abajo como si fuera secuencial —que es como lo piensa una persona—. Dos piezas: async marca una función como asíncrona, y await «espera» a que una promesa se resuelva sin bloquear el resto del programa.
// Pedir datos a una API y usarlos. Se lee como pasos, aunque sea asíncrono.
async function mostrarPedido(id) {
const respuesta = await fetch(`/api/pedidos/${id}`); // espera la respuesta
const pedido = await respuesta.json(); // espera el cuerpo
console.log(pedido.total); // recién ahora, con el dato
}La clave: await pausa esa función hasta que llega el resultado, pero no congela el resto del programa —la página sigue respondiendo—. El código queda tan legible como uno secuencial, sin pirámides ni anidamiento. Por eso await solo funciona dentro de una función async: es la que puede pausarse y retomarse.
Manejar errores
Las tareas asíncronas fallan más que las normales —la red se cae, la API devuelve error—, así que manejar los fallos no es opcional. La ventaja de async/await es que los errores se manejan con el mismo try/catch del código normal:
async function mostrarPedido(id) {
try {
const respuesta = await fetch(`/api/pedidos/${id}`);
if (!respuesta.ok) throw new Error(`Error ${respuesta.status}`);
const pedido = await respuesta.json();
return pedido;
} catch (error) {
console.error('No se pudo cargar el pedido:', error);
// mostrar un mensaje al usuario, reintentar, etc.
}
}Un detalle que se olvida a menudo, y que conecta con las APIs REST: fetch no lanza error por un código 404 o 500; solo falla si no hay red. Por eso hay que comprobar respuesta.ok a mano y lanzar el error uno mismo. Envolver lo asíncrono en try/catch es parte de escribir código robusto, como se ve al depurar.
Varias tareas a la vez
Un error de rendimiento clásico: esperar tareas independientes una tras otra cuando podrían correr a la vez. Si pedís tres cosas que no dependen entre sí, await tras await las hace secuenciales y sumás los tres tiempos. Para lanzarlas juntas está Promise.all:
// Lento: una tras otra. Suma los tres tiempos.
const usuario = await fetch('/api/usuario');
const pedidos = await fetch('/api/pedidos');
const favoritos = await fetch('/api/favoritos');
// Rápido: las tres a la vez. Espera a que la más lenta termine.
const [usuario, pedidos, favoritos] = await Promise.all([
fetch('/api/usuario'),
fetch('/api/pedidos'),
fetch('/api/favoritos'),
]);La regla: si las tareas dependen una de otra (necesitás el resultado de la primera para la segunda), van con await secuencial; si son independientes, van juntas con Promise.all. Confundir esto es una de las causas más comunes de interfaces lentas sin motivo aparente.
Errores frecuentes
- Olvidar el
await. Sin él, usás la promesa (el ticket) en vez del valor: el error más común al empezar. - Creer que
fetchfalla con 404 o 500. No: hay que comprobarrespuesta.oky lanzar el error a mano. - No manejar errores. Lo asíncrono falla seguido; sin
try/catch, el fallo pasa desapercibido o rompe todo. - Esperar tareas independientes en serie. Sumás tiempos sin necesidad; usá
Promise.all. - Usar
awaitfuera de una funciónasync. No funciona:awaitvive dentro deasync. - Volver al anidamiento de callbacks. async/await existe para evitar la pirámide; no la reintroduzcas.
- Bloquear con bucles pesados. Async no vuelve paralelo el cálculo; una tarea de CPU intensa igual congela.
Preguntas frecuentes
¿Por qué se dice que JavaScript no espera?
Porque cuando JavaScript pide algo que tarda —datos por red, la lectura de un archivo, un temporizador— no se queda detenido esperando el resultado, sino que deja esa tarea en marcha, sigue ejecutando el resto del programa, y vuelve a atender el resultado cuando está listo. La razón es que si esperara cada respuesta lenta, la página entera se congelaría durante ese tiempo: los botones no responderían y nada se movería. Al no esperar, la interfaz sigue viva mientras los datos llegan de fondo. El precio de esa fluidez es que el resultado de una tarea lenta llega en otro momento, más adelante, y hay que indicarle al programa qué hacer cuando eso ocurra. Entender esta idea es previo a cualquier sintaxis, porque las promesas, los callbacks y async con await son todos formas distintas de responder a la misma pregunta: qué hacer cuando el resultado esté listo.
¿Qué es una promesa?
Es un objeto que representa un resultado que todavía no existe pero que va a llegar, algo así como un ticket de guardarropa: no tenés el abrigo en la mano, pero tenés algo que podrás canjear por él. Una promesa está siempre en uno de tres estados: pendiente, mientras la tarea sigue en curso; resuelta, cuando la tarea terminó bien y entrega un valor; o rechazada, cuando falló y entrega un error. Las promesas nacieron para resolver un problema histórico llamado infierno de callbacks, en el que encadenar varias tareas asíncronas dependientes producía funciones anidadas unas dentro de otras en una pirámide ilegible; las promesas permiten encadenar esas tareas de forma plana. Hoy, sin embargo, casi no se escriben promesas a mano: se usan a través de la sintaxis de async y await, que es mucho más legible y está construida sobre ellas.
¿Qué hace exactamente await?
La palabra await pausa la ejecución de la función asíncrona en la que está hasta que la promesa que acompaña se resuelve, y entonces devuelve su valor. Lo importante es que esa pausa no congela el resto del programa: solo suspende esa función concreta, mientras la interfaz y las demás tareas siguen funcionando normalmente. El gran beneficio es que permite escribir código asíncrono que se lee de arriba hacia abajo como si fuera secuencial, que es como lo piensa naturalmente una persona, en lugar de encadenar promesas o anidar funciones. Por eso await solo puede usarse dentro de una función marcada como async: es esa marca la que habilita a la función a pausarse y retomarse. Olvidar el await es el error más común al empezar, porque sin él el programa sigue adelante usando la promesa sin resolver en lugar del valor que se esperaba.
¿Cómo manejo los errores en código asíncrono?
Con la misma estructura de try y catch que se usa en el código normal, que es una de las grandes ventajas de async y await. Se envuelve el bloque asíncrono en un try, y si alguna de las operaciones esperadas falla, el control salta al catch, donde se maneja el error: mostrar un mensaje al usuario, registrar el problema, reintentar. Esto es importante porque las tareas asíncronas fallan más que las normales, ya que dependen de cosas como la red o servicios externos que pueden caerse. Un detalle que se olvida a menudo es que la función de pedir datos por red no considera un fallo que el servidor responda con un código de error como el de recurso no encontrado o error interno: solo falla si directamente no hay red. Por eso hay que comprobar a mano si la respuesta fue exitosa y, si no lo fue, lanzar el error uno mismo para que el catch pueda atraparlo.
¿Cómo ejecuto varias tareas asíncronas a la vez?
Con un método que lanza varias promesas juntas y espera a que todas terminen, en lugar de esperarlas una tras otra. La distinción clave es si las tareas dependen entre sí o no. Si el resultado de una hace falta para empezar la siguiente, entonces son dependientes y se esperan en secuencia, una después de otra. Pero si son independientes, esperarlas de a una es un error de rendimiento muy común, porque se suman todos los tiempos innecesariamente: mientras se espera la primera, las otras podrían haber estado ejecutándose. Lanzándolas todas a la vez y esperando el conjunto, el tiempo total pasa a ser el de la más lenta en lugar de la suma de todas. Reconocer cuándo unas tareas son independientes y pueden correr en paralelo es una de las diferencias entre una interfaz que carga rápido y una que se siente lenta sin motivo aparente.
¿La programación asíncrona hace que mi código corra en paralelo?
No en el sentido de ejecutar varias operaciones de cálculo al mismo tiempo. JavaScript ejecuta el código en un solo hilo, es decir, una instrucción a la vez. Lo que permite la asincronía es no quedarse bloqueado esperando tareas que dependen de algo externo y lento, como la red o el disco: durante esa espera, en la que el programa no está calculando nada sino simplemente aguardando una respuesta, puede atender otras cosas. Por eso varias peticiones de red sí pueden estar en curso simultáneamente, porque el trabajo lo hacen otros sistemas y JavaScript solo espera sus respuestas. Pero si tenés una tarea que exige cálculo intensivo del procesador, como procesar una imagen enorme en un bucle, esa sí bloquea el hilo y congela la interfaz aunque la envuelvas en código asíncrono, porque no hay ninguna espera externa que aprovechar. Para esos casos existen otros mecanismos pensados para el trabajo pesado.
Fuentes
Documentación oficial y material de la comunidad consultados para esta guía. Fecha de consulta: 28 de julio de 2026.
Aportes de la comunidad Underc0de
- Underc0de, foro. Sección Desarrollo web. Consultas sobre JavaScript, asincronía y consumo de APIs.
- Underc0de, foro. Sección Programación. Fundamentos y patrones de programación.
Documentación oficial
- Mozilla. Asincronía en JavaScript. La introducción de referencia al tema, en español.
- Mozilla. Promise. La referencia del objeto promesa y sus métodos.
- Mozilla. Funciones async. La sintaxis de async y await.
- ECMA. Especificación de ECMAScript. El estándar del lenguaje.