En esta página
Async/Await
Qué es async/await?
async/await es azucar sintáctico sobre las promesas que hace que el código asincrono se lea como si fuera sincrono. Introducido en ES2017, es la forma preferida de trabajar con operaciones asincronas en JavaScript moderno.
La palabra clave async
Marcar una función como async tiene dos efectos:
- La función siempre retorna una promesa
- Permite usar
awaitdentro de ella
async function ejemplo() {
return 42; // equivale a return Promise.resolve(42)
}La palabra clave await
await pausa la ejecución de la función async hasta que la promesa se resuelva, y retorna su valor:
async function obtener() {
const resultado = await algunaPromesa(); // pausa aquí
console.log(resultado); // se ejecuta cuando la promesa se resuelve
}Manejo de errores
En lugar de .catch(), usamos bloques try/catch que son familiares de la programación sincrona:
async function cargar() {
try {
const datos = await operacionRiesgosa();
return datos;
} catch (error) {
console.error('Fallo:', error.message);
return valorPorDefecto;
} finally {
limpiarRecursos();
}
}El bloque catch captura tanto errores de la promesa rechazada como excepciones lanzadas con throw.
Secuencial vs paralelo
Este es uno de los errores más comunes con async/await:
Secuencial (lento)
const a = await peticion1(); // espera 1s
const b = await peticion2(); // espera 1s más
// Total: 2 segundosParalelo (rápido)
const [a, b] = await Promise.all([peticion1(), peticion2()]);
// Total: 1 segundo (se ejecutan al mismo tiempo)Usa la versión secuencial solo cuando una petición depende del resultado de la anterior.
Patrones útiles
Procesar una lista de items
Dependiendo de si los items son independientes o no:
- Secuencial — Usa
for...ofconawaitdentro del bucle - Paralelo — Usa
.map()con funciones async yPromise.all
Retry con backoff exponencial
Un patrón robusto para reintentar operaciones que pueden fallar temporalmente (ej. peticiones de red). Cada reintento espera exponencialmente más tiempo (1s, 2s, 4s...).
Top-level await
En módulos ES, puedes usar await directamente en el scope del módulo:
// config.js (módulo ES)
const response = await fetch('/api/config');
export const config = await response.json();async/await vs .then()
| Aspecto | async/await | .then() |
|---|---|---|
| Legibilidad | Más legible, flujo lineal | Puede anidarse |
| Errores | try/catch familiar | .catch() en cadena |
| Debugging | Stack traces claros | Stack traces fragmentados |
| Paralelismo | Necesita Promise.all | Natural con multiples .then |
En la práctica, async/await se prefiere para la mayoria de los casos. Usa .then() cuando necesites composicion de promesas rápida o en callbacks cortos.
Práctica
- Convierte .then() a async/await: Toma una cadena de promesas existente con
.then().catch()y reescribela usandoasync/awaitcontry/catch/finally. Verifica que el comportamiento sea identico. - Ejecuta tareas en paralelo: Escribe una funcion async que use
Promise.allpara ejecutar 3 llamadas simuladas (usandosetTimeoutenvuelto en promesas) en paralelo, y mide el tiempo total conperformance.now(). - Implementa un retry basico: Crea una funcion
conReintentos(fn, intentos)que reintente ejecutarfnhastaintentosveces si falla. Probala con una funcion que falle aleatoriamente.
En la siguiente leccion aprenderemos a usar la Fetch API para hacer peticiones HTTP.
// Función async - siempre retorna una promesa
async function obtenerUsuario(id) {
// await pausa la ejecución hasta que la promesa se resuelva
const respuesta = await fetch(`/api/users/${id}`);
const usuario = await respuesta.json();
return usuario;
}
// Manejo de errores con try/catch
async function cargarDatos() {
try {
const usuario = await obtenerUsuario(1);
const posts = await fetch(`/api/users/${usuario.id}/posts`);
const datos = await posts.json();
console.log('Posts:', datos);
} catch (error) {
console.error('Error al cargar:', error.message);
} finally {
console.log('Carga finalizada');
}
}
// Peticiones en paralelo con await
async function cargarDashboard() {
// MAL: secuencial (lento)
// const users = await fetch('/api/users').then(r => r.json());
// const posts = await fetch('/api/posts').then(r => r.json());
// BIEN: paralelo (rápido)
const [users, posts] = await Promise.all([
fetch('/api/users').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
]);
return { users, posts };
}
// Arrow function async
const obtenerPerfil = async (id) => {
const res = await fetch(`/api/profiles/${id}`);
if (!res.ok) {
throw new Error(`HTTP ${res.status}`);
}
return res.json();
};
// Patron: procesar items secuencialmente
async function procesarSecuencial(urls) {
const resultados = [];
for (const url of urls) {
const res = await fetch(url);
const data = await res.json();
resultados.push(data);
}
return resultados;
}
// Patron: procesar items en paralelo
async function procesarParalelo(urls) {
const promesas = urls.map(async (url) => {
const res = await fetch(url);
return res.json();
});
return Promise.all(promesas);
}
// Patron: retry con backoff exponencial
async function conReintentos(fn, intentos = 3) {
for (let i = 0; i < intentos; i++) {
try {
return await fn();
} catch (error) {
if (i === intentos - 1) throw error;
const espera = Math.pow(2, i) * 1000;
console.log(`Reintentando en ${espera}ms...`);
await new Promise(r => setTimeout(r, espera));
}
}
}
// Uso del retry
const datos = await conReintentos(
() => fetch('/api/datos').then(r => r.json()),
3
);
Inicia sesión para guardar tu progreso