async/await es la sintaxis para trabajar con Promises introducida en ES2017. No reemplaza a las Promises: las envuelve en una sintaxis más parecida al código síncrono, lo que hace que el código asíncrono sea mucho más legible y fácil de depurar. Comprender que async/await es azúcar sobre Promises es fundamental para entender su comportamiento.
async function y await
Una función marcada con async siempre devuelve una Promise. await pausa la ejecución de la función async hasta que la Promise se resuelve, sin bloquear el hilo principal:
// Con Promises:
function obtenerUsuario(id) {
return fetch(`/api/usuarios/${id}`)
.then(r => {
if (!r.ok) throw new Error('Error HTTP: ' + r.status);
return r.json();
})
.then(datos => datos.usuario);
}
// Con async/await (equivalente, más legible):
async function obtenerUsuario(id) {
const r = await fetch(`/api/usuarios/${id}`);
if (!r.ok) throw new Error('Error HTTP: ' + r.status);
const datos = await r.json();
return datos.usuario;
}
// Consumir una función async:
obtenerUsuario(1).then(u => console.log(u));
// O con await (dentro de otra función async):
async function main() {
const usuario = await obtenerUsuario(1);
console.log(usuario);
}
Manejo de errores con try/catch
Con async/await, los errores de Promises rechazadas se capturan con try/catch, igual que los errores síncronos:
async function cargarDatos(url) {
try {
const respuesta = await fetch(url);
if (!respuesta.ok) {
throw new Error(`HTTP ${respuesta.status}: ${respuesta.statusText}`);
}
const datos = await respuesta.json();
return datos;
} catch (err) {
// Captura tanto errores de red como el throw manual
console.error('Error al cargar:', err.message);
throw err; // Re-lanzar para que el llamador pueda manejarlo
} finally {
console.log('Petición terminada');
}
}
// Manejo en el punto de uso:
async function inicializar() {
try {
const config = await cargarDatos('/api/config');
const usuarios = await cargarDatos('/api/usuarios');
console.log('Inicializado con', usuarios.length, 'usuarios');
} catch (err) {
console.error('No se pudo inicializar:', err.message);
mostrarPantallaError();
}
}
await en bucles: cuidado con la serialización
Usar await dentro de un bucle for serializa las operaciones: cada una espera a que la anterior termine. Esto puede ser muy lento si las operaciones son independientes:
// MAL: secuencial (lento si las peticiones son independientes)
async function cargarTodosSecuencial(ids) {
const resultados = [];
for (const id of ids) {
const usuario = await obtenerUsuario(id); // Espera cada vez
resultados.push(usuario);
}
return resultados;
}
// Con 10 peticiones de 200ms cada una: ~2000ms
// BIEN: paralelo con Promise.all
async function cargarTodosParalelo(ids) {
const promesas = ids.map(id => obtenerUsuario(id));
return await Promise.all(promesas);
}
// Con 10 peticiones de 200ms cada una: ~200ms
// Cuándo el bucle secuencial SÍ tiene sentido:
// Cuando cada petición depende del resultado de la anterior
async function paginacionSecuencial() {
let pagina = 1;
let hayMas = true;
const todos = [];
while (hayMas) {
const { datos, siguiente } = await fetch(`/api?page=${pagina}`).then(r => r.json());
todos.push(...datos);
hayMas = !!siguiente;
pagina++;
}
return todos;
}
Top-level await
En módulos ES (archivos con type="module"), await puede usarse directamente en el nivel superior, sin necesidad de envolver en una función async:
// En un módulo ES (top-level await)
const config = await fetch('/api/config').then(r => r.json());
const { tema, idioma } = config;
export { tema, idioma };
// Los módulos que importen este esperarán a que el await termine
async/await con Promise.all y Promise.allSettled
async/await y los combinadores de Promises funcionan perfectamente juntos:
async function cargaCompleta(usuarioId) {
// Lanzar todas las peticiones en paralelo
const [usuario, pedidos, preferencias] = await Promise.all([
obtenerUsuario(usuarioId),
obtenerPedidos(usuarioId),
obtenerPreferencias(usuarioId)
]);
return { usuario, pedidos, preferencias };
}
// Con allSettled para no fallar si algo opcional no carga:
async function cargaRobusta(usuarioId) {
const resultados = await Promise.allSettled([
obtenerUsuario(usuarioId),
obtenerPedidos(usuarioId), // puede fallar
obtenerRecomendaciones(usuarioId) // puede fallar
]);
const [resUsuario, resPedidos, resReco] = resultados;
return {
usuario: resUsuario.status === 'fulfilled' ? resUsuario.value : null,
pedidos: resPedidos.status === 'fulfilled' ? resPedidos.value : [],
recomendaciones: resReco.status === 'fulfilled' ? resReco.value : []
};
}
El error más frecuente con async/await es olvidar que await solo puede usarse dentro de funciones async. Fuera de ellas, necesitas .then() o top-level await en módulos ES. El segundo error más frecuente es no poner await donde hace falta y recibir una Promise en lugar del valor esperado.
