Lanzar varias peticiones asíncronas en paralelo es uno de los casos más comunes en aplicaciones JavaScript reales. Los cuatro métodos estáticos de Promise all, allSettled, race y any resuelven cada variante de ese problema, pero con semánticas distintas que vale la pena dominar para elegir la herramienta adecuada sin prueba y error.
Promise.all: éxito total o fallo inmediato
Promise.all espera a que todas las promesas del array se resuelvan. Si alguna rechaza, el conjunto rechaza de inmediato y el resto se ignora. Es la elección cuando necesitas todos los resultados o no puedes continuar.
const [usuario, pedidos, inventario] = await Promise.all([
fetch('/api/usuario/42').then(r => r.json()),
fetch('/api/pedidos?user=42').then(r => r.json()),
fetch('/api/inventario').then(r => r.json()),
]);
// Si cualquiera falla, el await lanza un error
Las tres peticiones salen al mismo tiempo. En serie tardarían la suma de los tres tiempos; en paralelo con Promise.all tardas el tiempo de la más lenta.
Promise.allSettled: resultados completos sin importar errores
Promise.allSettled espera a que todas terminen, ya sea con éxito o con error, y devuelve un array de objetos con status ("fulfilled" o "rejected") y value o reason. Es útil para informes donde necesitas saber qué falló pero no quieres abortar todo.
const resultados = await Promise.allSettled([
sincronizarModulo('pagos'),
sincronizarModulo('envios'),
sincronizarModulo('stock'),
]);
const errores = resultados
.filter(r => r.status === 'rejected')
.map(r => r.reason.message);
if (errores.length) {
console.warn('Módulos con error:', errores);
}
Promise.race: el primero que termine gana
Promise.race resuelve (o rechaza) en cuanto la primera promesa del array se resuelve o rechaza. El patrón más útil es implementar un timeout controlado sin depender de librerías externas:
function conTimeout(promesa, ms) {
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error(`Timeout tras ${ms}ms`)), ms)
);
return Promise.race([promesa, timeout]);
}
try {
const datos = await conTimeout(fetch('/api/pesado').then(r => r.json()), 3000);
console.log(datos);
} catch (err) {
console.error(err.message); // "Timeout tras 3000ms" o error de red
}
Promise.any: el primero que tenga éxito
Promise.any (ES2021) resuelve con la primera promesa que tenga éxito, ignorando los rechazos intermedios. Solo rechaza si todas fallan, con un AggregateError. Es ideal para redundancia: intentar varios endpoints y quedarte con el más rápido disponible.
const cdn = await Promise.any([
fetch('https://cdn1.ejemplo.com/asset.js').then(r => r.text()),
fetch('https://cdn2.ejemplo.com/asset.js').then(r => r.text()),
fetch('https://cdn3.ejemplo.com/asset.js').then(r => r.text()),
]);
// Obtiene el recurso del CDN más rápido que responda sin error
Limitar la concurrencia
Lanzar cien peticiones simultáneas puede saturar el servidor o el navegador. Una función simple de pool de concurrencia resuelve el problema procesando en lotes:
async function poolConcurrencia(items, limite, fn) {
const resultados = [];
for (let i = 0; i < items.length; i += limite) {
const lote = items.slice(i, i + limite);
const parcial = await Promise.all(lote.map(fn));
resultados.push(...parcial);
}
return resultados;
}
// Procesar 50 IDs de 5 en 5
const ids = Array.from({ length: 50 }, (_, i) => i + 1);
const datos = await poolConcurrencia(ids, 5, id =>
fetch(`/api/item/${id}`).then(r => r.json())
);
Cuándo usar cada método
Como regla general: usa Promise.all cuando necesites todos los resultados y un fallo debe abortar; Promise.allSettled cuando quieras resultados completos aunque haya errores parciales; Promise.race para timeouts o carreras de velocidad; y Promise.any para redundancia con varios proveedores. Conocer los cuatro te ahorra escribir lógica de combinación manual que acaba siendo más difícil de mantener.
