Articulo publicado el
Domina la programación asíncrona desde cero
La programación asincrónica es el corazón de cualquier aplicación JavaScript moderna. Desde solicitudes HTTP hasta temporizadores, casi todo en el navegador es asincrónico. En esta guía, te llevaré desde los Promises básicos hasta async/await avanzado, con ejemplos prácticos y patrones que realmente usas en producción.
JavaScript es single-threaded: ejecuta una sola cosa a la vez. Sin asincronía, esperar una respuesta del servidor congelaría la interfaz completamente.
La asincronía permite que las operaciones largas ocurran "en segundo plano" mientras el código continúa ejecutándose.
// ❌ Sincrónico: congela todo
console.log("Inicio");
const data = fetch("/api/datos"); // Esto es imposible en sync
console.log(data);
console.log("Fin");
// ✅ Asincrónico: no congela
console.log("Inicio");
fetch("/api/datos").then((data) => console.log(data));
console.log("Fin");
// Salida: "Inicio", "Fin", [data]
Un Promise es un objeto que representa el resultado eventual de una operación asincrónica. Tiene tres estados posibles:
const promise = new Promise((resolve, reject) => {
// Simulamos una operación asincrónica
setTimeout(() => {
const success = true;
if (success) {
resolve("Operación exitosa");
} else {
reject("Algo salió mal");
}
}, 1000);
});
promise
.then((result) => console.log(result)) // Ejecuta si cumple
.catch((error) => console.error(error)); // Ejecuta si rechaza
Los Promises pueden encadenarse usando .then(), lo que permite que cada paso dependa del anterior:
fetch("/api/usuario/1")
.then((response) => response.json())
.then((user) => {
console.log("Usuario:", user);
return fetch(`/api/posts/${user.id}`);
})
.then((response) => response.json())
.then((posts) => console.log("Posts:", posts))
.catch((error) => console.error("Error:", error));
Async/await es una forma más legible de escribir código asincrónico usando Promises. Convierte el código asincrónico en algo que se ve como sincrónico.
async function fetchUser(id) {
try {
const response = await fetch(`/api/usuario/${id}`);
const user = await response.json();
return user;
} catch (error) {
console.error("Error al obtener usuario:", error);
}
}
// Uso
fetchUser(1).then((user) => console.log(user));
async: Marca una función como asincrónica. Siempre devuelve un Promise.await: Pausa la ejecución hasta que el Promise se resuelva. Solo funciona dentro de funciones async.async function ejemplo() {
console.log("1. Inicio");
const data = await fetch("/api/datos"); // Pausa aquí hasta recibir respuesta
console.log("2. Datos recibidos:", data);
console.log("3. Fin");
}
// Salida:
// 1. Inicio
// [Espera a que se resuelva el fetch]
// 2. Datos recibidos: Response {...}
// 3. Fin
function obtenerDatos() {
return fetch("/api/usuario")
.then((response) => response.json())
.then((user) => fetch(`/api/posts/${user.id}`))
.then((response) => response.json())
.then((posts) => ({ user: user, posts: posts }))
.catch((error) => console.error(error));
}
async function obtenerDatos() {
try {
const userResponse = await fetch("/api/usuario");
const user = await userResponse.json();
const postsResponse = await fetch(`/api/posts/${user.id}`);
const posts = await postsResponse.json();
return { user, posts };
} catch (error) {
console.error(error);
}
}
Async/await es más legible y se parece más a código sincrónico.
async function descargarArchivo(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.blob();
return data;
} catch (error) {
console.error("Error al descargar:", error.message);
throw error; // Re-lanzar si quieres
}
}
fetch(url)
.then((response) => {
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.blob();
})
.catch((error) => console.error("Error:", error.message));
async function procesarSecuencial() {
const user = await fetch("/api/usuario/1").then((r) => r.json());
const posts = await fetch(`/api/posts/${user.id}`).then((r) => r.json());
const comments = await fetch(`/api/comments/${posts[0].id}`).then((r) => r.json());
// Tiempo total: suma de todos los tiempos (lento)
return { user, posts, comments };
}
async function procesarParalelo() {
// Todas las solicitudes comienzan al mismo tiempo
const [user, posts, comments] = await Promise.all([fetch("/api/usuario/1").then((r) => r.json()), fetch("/api/posts/1").then((r) => r.json()), fetch("/api/comments/1").then((r) => r.json())]);
// Tiempo total: el máximo de los tres (más rápido)
return { user, posts, comments };
}
Promise.all(): Espera a que TODOS los Promises se resuelvan. Si uno falla, rechaza.
Promise.all([promise1, promise2, promise3])
.then(([result1, result2, result3]) => {
console.log("Todos completados:", result1, result2, result3);
})
.catch((error) => console.error("Alguno falló:", error));
Promise.race(): Devuelve el resultado del PRIMERO que se resuelva.
Promise.race([promise1, promise2])
.then((result) => console.log("El primero en terminar:", result))
.catch((error) => console.error("El primero falló:", error));
Si necesitas que se ejecuten todos los Promises sin importar si fallan:
const promesas = [fetch("/api/1").then((r) => r.json()), fetch("/api/2").then((r) => r.json()), fetch("/api/3").then((r) => r.json())];
const resultados = await Promise.allSettled(promesas);
// Devuelve: [
// { status: 'fulfilled', value: data1 },
// { status: 'rejected', reason: Error },
// { status: 'fulfilled', value: data3 }
// ]
resultados.forEach((resultado, idx) => {
if (resultado.status === "fulfilled") {
console.log(`Promesa ${idx} exitosa:`, resultado.value);
} else {
console.log(`Promesa ${idx} falló:`, resultado.reason);
}
});
async function fetchConReintentos(url, maxIntentos = 3) {
for (let intento = 1; intento <= maxIntentos; intento++) {
try {
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return await response.json();
} catch (error) {
if (intento === maxIntentos) throw error;
// Esperar antes de reintentar (backoff exponencial)
const delay = Math.pow(2, intento - 1) * 1000;
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
}
// Usa
fetchConReintentos("/api/datos", 3)
.then((data) => console.log(data))
.catch((error) => console.error("Falló después de 3 intentos"));
function conTimeout(promise, tiempoMs) {
return Promise.race([promise, new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), tiempoMs))]);
}
// Uso
conTimeout(fetch("/api/datos"), 5000)
.then((response) => response.json())
.catch((error) => console.error("Falló o excedió timeout"));
La programación asincrónica es fundamental en JavaScript moderno. Dominarla te abrirá las puertas a aplicaciones más responsivas, escalables y profesionales.
Estos articulos pueden interesarte si te gusto este articulo.
Optimiza el rendimiento de tu aplicación limitando cuántas veces se ejecutan listeners de eventos y funciones costosas
Cómo reducir listeners y mejorar el rendimiento usando bubbling de eventos
Un componente accesible, reutilizable y escalable con API programatica, teclado, ARIA y registro interno