Aunque JavaScript utiliza un modelo de ejecución de un solo hilo (single-threaded), es capaz de gestionar miles de operaciones simultáneas como peticiones a APIs, lectura de archivos o eventos del usuario. Esto es posible gracias a la programación asíncrona. Entender el funcionamiento de las Promesas y la sintaxis async/await es un requisito indispensable para cualquier desarrollador web moderno.
La evolución del flujo asíncrono: De Callbacks a Async/Await
La era de los Callbacks
El infierno de los Callbacks (Callback Hell)
// La anidación profunda vuelve el código ilegible
obtenerUsuario(userId, (err, user) => {
if (err) return gestionarError(err);
obtenerPedidos(user.id, (err, orders) => {
if (err) return gestionarError(err);
obtenerDetallesPedido(orders[0].id, (err, details) => {
if (err) return gestionarError(err);
console.log(details); // ¡Por fin!
});
});
});
Promises: Una forma más elegante
Encadenamiento de Promesas
obtenerUsuario(userId) .then(user => obtenerPedidos(user.id)) .then(orders => obtenerDetallesPedido(orders[0].id)) .then(details => console.log(details)) .catch(err => gestionarError(err)) .finally(() => ocultarCargador());
Async/Await: Legibilidad superior
Sintaxis moderna Async/Await
async function obtenerPedidoCompleto(userId) {
try {
const user = await obtenerUsuario(userId);
const orders = await obtenerPedidos(user.id);
const details = await obtenerDetallesPedido(orders[0].id);
return details;
} catch (err) {
gestionarError(err);
} finally {
ocultarCargador();
}
}
Creación de Promesas desde cero
Uso del constructor Promise
function esperar(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function peticionConTiempoLimite(url, ms) {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => reject(new Error("Tiempo excedido")), ms);
fetch(url)
.then(response => {
clearTimeout(timer);
resolve(response);
})
.catch(err => {
clearTimeout(timer);
reject(err);
});
});
}
Gestión de operaciones concurrentes
Promise.all — Ejecución en paralelo
// Todas deben resolverse con éxito (falla ante el primer error)
const [usuarios, productos, pedidos] = await Promise.all([
fetch("/api/usuarios").then(r => r.json()),
fetch("/api/productos").then(r => r.json()),
fetch("/api/pedidos").then(r => r.json()),
]);
// allSettled — Retorna el estado de cada promesa, aunque algunas fallen
const resultados = await Promise.allSettled([
fetch("/api/usuarios"),
fetch("/api/productos"),
fetch("/api/pedidos"),
]);
resultados.forEach(res => {
if (res.status === "fulfilled") {
console.log("Éxito:", res.value);
} else {
console.log("Error:", res.reason);
}
});
Promise.race — La primera en terminar gana
// Muy útil para timeouts o carreras de red
const resultado = await Promise.race([
fetch("/api/datos"),
esperar(5000).then(() => { throw new Error("Tiempo agotado!"); })
]);
Patrones efectivos para manejo de errores
Wrapper estilo Go (sin bloques try/catch masivos)
// Retorna una tupla [error, datos]
async function to(promesa) {
try {
const data = await promesa;
return [null, data];
} catch (err) {
return [err, null];
}
}
// Uso práctico
const [err, user] = await to(obtenerUsuario(123));
if (err) {
console.error("Error al obtener el usuario:", err.message);
return;
}
console.log(user);
Errores frecuentes que debes evitar
- Olvidar el
await: Sin la palabra clave, obtendrás el objeto Promise en lugar de su resultado final. - Secuencialidad innecesaria: No ejecutes promesas de forma lineal si son independientes; usa
Promise.all()para mejorar el rendimiento. - Ignorar rechazos: Todo proceso asíncrono debe tener un bloque
catcho una gestión centralizada de errores. - Async en bucles
forEach: Este método no espera a las promesas. Utiliza un buclefor...ofoPromise.all(arr.map(...)). - Promesas redundantes: Si una librería ya retorna una Promise, no necesitas envolverla de nuevo en un
new Promise().
Prueba nuestras herramientas gratuitas para desarrolladores
Optimiza y embellece tu código JavaScript al instante.