Inmersión en Promise.all: API y DB en Node.js
1. JavaScript vs runtime de Node.js
JavaScript sobre V8 es monohilo, ejecuta de arriba a abajo y resuelve promesas mediante la cola de microtareas. El runtime de Node.js se construye sobre V8 y libuv, donde libuv aporta el event loop y un pool de hilos, además de integrarse con la pila de red del sistema operativo para operaciones de entrada y salida asíncronas. La concurrencia en Node.js no aparece por añadir hilos al código JS, sino por delegar trabajo en el sistema operativo, en motores de base de datos y en los workers de libuv.
2. Cómo funciona Promise.all
Promise.all crea una promesa agregada que se suscribe a cada promesa de entrada, lleva la cuenta de resultados y pendientes, resuelve con un array si todas cumplen y rechaza en cuanto una falla. Sus resoluciones se programan en la cola de microtareas, que se atiende antes que los temporizadores y las devoluciones de llamada de I O.
3. Llamadas a API con Promise.all
Al lanzar varias peticiones fetch en paralelo con Promise.all, cada llamada abre un socket TCP. El sistema operativo gestiona DNS, el apretón de manos de TCP y TLS. libuv registra los sockets para saber cuándo están listos; al llegar datos, empuja callbacks de I O al event loop; y finalmente las promesas se resuelven vía microtareas. La concurrencia real proviene de la pila de red del sistema operativo, por eso podemos solapar varias peticiones sin bloquear el hilo de JS.
4. Consultas a base de datos con Promise.all
Cuando enviamos consultas en paralelo, el cliente escribe en los sockets de la base de datos, el motor como PostgreSQL las ejecuta en paralelo dentro de sus procesos o workers, los resultados se transmiten de vuelta, libuv notifica al event loop y las promesas se resuelven y se agregan mediante Promise.all. La concurrencia la proporciona el propio motor de la base de datos.
5. Event loop y microtareas
El event loop de Node.js recorre fases como temporizadores setTimeout y setInterval, callbacks pendientes, idle prepare, poll para nuevos eventos de I O como sockets listos, check para setImmediate y callbacks de cierre. Entre cada fase se ejecuta primero la cola de microtareas, de ahí que las resoluciones de promesas y Promise.all ocurran antes que los temporizadores o las devoluciones de llamada de I O.
6. libuv: red vs pool de hilos
La I O de red como fetch o sockets de base de datos usa mecanismos del sistema como epoll, kqueue o IOCP, sin hilos, lo que escala muy bien. El pool de hilos se usa cuando el sistema no ofrece APIs asíncronas, por ejemplo operaciones de sistema de archivos o funciones de criptografía intensivas como pbkdf2. El tamaño por defecto del pool es 4 y se controla con la variable UV_THREADPOOL_SIZE.
7. Benchmarks sencillo vs paralelo
Si ejecutas 5 consultas que duermen 1 segundo cada una de forma secuencial, el tiempo total ronda 5 segundos. Si las disparas en paralelo con Promise.all, el tiempo total ronda 1 segundo, porque solapas la espera de I O. La ganancia no proviene de paralelismo de JS, sino de delegar en la red y en el motor de base de datos.
8. Errores comunes en producción
Límites de tasa en APIs externas pueden provocar respuestas 429 o throttling si envías demasiadas peticiones concurrentes. Controla la concurrencia con utilidades como p limit o Bottleneck. El agotamiento del pool de conexiones de la base de datos provoca colas y picos de latencia; ajusta el tamaño del pool y procesa en lotes. Promise.all falla rápido ante el primer rechazo; si necesitas resiliencia usa Promise.allSettled para obtener el estado de todas. En cargas muy grandes, un único Promise.all puede consumir mucha memoria al acumular todos los resultados; considera procesar en streaming o por ventanas.
9. Alternativas a Promise.all
Promise.allSettled para obtener todos los resultados incluso con fallos, Promise.any para resolver con el primer éxito útil cuando usas servicios redundantes, y Promise.race para resolver o rechazar con el primer resultado asentado, ideal para timeouts y carreras controladas.
Conclusiones clave
Promise.all coordina tareas asíncronas, no convierte a JavaScript en paralelo. La concurrencia en APIs viene de la red del sistema operativo y la de bases de datos de los propios motores. El event loop de Node.js y libuv orquestan todo. Úsalo con cuidado teniendo en cuenta límites de pool, límites de tasa y memoria. Los benchmarks muestran reducciones reales de latencia al solapar I O, pero también dejan ver los riesgos de escalabilidad si no se controla la concurrencia.
Pensamiento final
Promise.all no es magia, es orquestación. El paralelismo que se percibe proviene del kernel del sistema operativo, de libuv y de sistemas externos. La labor de ingeniería es conocer no solo que corre concurrentemente, sino cuánta concurrencia soporta el sistema antes de degradarse.
Cómo te ayuda Q2BSTUDIO
En Q2BSTUDIO somos una empresa de desarrollo de software y aplicaciones a medida, especialistas en inteligencia artificial, ciberseguridad, servicios cloud AWS y Azure, servicios de inteligencia de negocio con power bi, agentes IA e ia para empresas. Diseñamos arquitecturas Node.js eficientes, optimizamos pools, aplicamos control de concurrencia y observabilidad para llevar tus microservicios y pipelines de datos a producción con garantías. Si buscas construir plataformas robustas y escalables, descubre cómo abordamos proyectos de software a medida y aplicaciones a medida aplicando patrones asíncronos y principios de resiliencia. Y si quieres acelerar la ejecución de tareas repetitivas y flujos orquestados con control de carga, te acompañamos con nuestra experiencia en automatización de procesos para que Promise.all y otras estrategias de concurrencia se integren de forma segura y medible en tu stack.