Yeda AI Tips · #116

English

forEach no espera nada

Un callback async dentro de forEach no espera nada. Tu bucle termina antes de que el trabajo termine — y como el código se ve correcto, este es uno de los bugs asíncronos más comunes que un asistente de IA escribe felizmente por ti.

Por qué forEach no puede esperar

MDN lo dice sin rodeos: "forEach() expects a synchronous function — it does not wait for promises." Una función async siempre devuelve una promesa, pero forEach descarta ese valor de retorno. Llama a tu callback una vez por elemento, uno tras otro, y sigue adelante. El resultado:

const ratings = [5, 4, 5];
let sum = 0;

ratings.forEach(async (rating) => {
  sum = await sum + rating;
});

console.log(sum); // 0 — not 14

Cada callback se inicia; ninguno termina antes de que corra el console.log. Los fallos peores tienen la misma forma: escrituras a la base de datos que llegan después de enviar la respuesta, archivos cerrados mientras todavía se escriben, un try/catch alrededor del bucle que no atrapa nada porque los rechazos escapan como unhandled promise rejections.

Elige tu intención: dos patrones que sí esperan

La solución no es un wrapper ingenioso sobre forEach — es declarar qué comportamiento quieres.

Secuencial — importan el orden y la contrapresión. Usa for...of con await adentro. Cada iteración termina antes de que empiece la siguiente, los errores aparecen en el await, y nunca disparas 10,000 solicitudes a la vez:

for (const user of users) {
  await sendEmail(user); // one at a time, in order
}

Concurrente — el orden no importa. Usa Promise.all sobre un map. Todo el trabajo arranca de inmediato, y la función que lo rodea se pausa hasta que todas las promesas se resuelven:

const results = await Promise.all(users.map((u) => fetchProfile(u.id)));

Promise.all es fail-fast: se rechaza en cuanto cualquiera de las promesas de entrada se rechaza, con ese primer error. Cuando necesitas todos los resultados aunque algunos fallen, usa Promise.allSettled, que siempre espera y devuelve {status, value|reason} por elemento.

Reglas prácticas

SituaciónUsaPor qué
Importa el orden, o cada paso depende del anteriorfor...of + awaitEstrictamente secuencial, manejo natural de errores
Trabajo independiente, lo quieres rápido, un fallo debe abortarawait Promise.all(items.map(fn))Concurrente, fail-fast
Trabajo independiente, necesitas cada resultado aunque haya fallosawait Promise.allSettled(items.map(fn))Nunca corta antes de tiempo
Efectos secundarios síncronos sobre un valor síncronoforEach (callback no async)Bien — no hay nada que esperar
Cualquier callback asyncnunca forEachLa promesa se descarta

Notas para usuarios avanzados

Recursos

¿Construyendo una funcionalidad con IA? Yeda AI diseña, audita y entrega sistemas LLM de producción.

Habla con nosotros · Lee el blog · Read in English