La asincronía en Node.js es la clave para aprovechar todo el potencial del event loop sin bloquearlo. Si programas en Node, dominar promesas, promise.all y generators te permite ejecutar tareas en paralelo o de forma controlada, manteniendo el rendimiento alto incluso con procesos pesados.
Node.js trabaja por naturaleza con callbacks, funciones que se ejecutan cuando termina cierto proceso. Pero en la práctica moderna, la promesa se popularizó por comodidad: representa un valor a futuro que aún no tenemos y que llegará cuando termine la ejecución asíncrona, ya sea con un resultado o con un error [0:32].
¿Qué es una promesa en Node.js y por qué importa?
Una promesa encapsula un proceso asíncrono y entrega su resultado cuando finaliza. Es la abstracción que reemplazó a los callbacks anidados en la mayoría de aplicaciones modernas.
¿Qué es una promesa en JavaScript? Es un objeto que representa el resultado futuro de una operación asíncrona. Puede resolverse con un valor (resolve) o fallar con un error (reject).
La documentación de Mozilla Developer Network es la mejor referencia pública para entender promesas a fondo, y cualquier persona que programe en Node.js debería revisarla [0:55].
¿Cómo ejecutar procesos en paralelo con promise.all?
El método promise.all permite lanzar varios procesos asíncronos al mismo tiempo y esperar a que todos terminen. Esa es la gran ventaja de Node frente a otras herramientas: puedes tener muchos procesos corriendo en su propio tiempo sin bloquear el event loop [1:25].
Para verlo en práctica, crea una carpeta Sincronia y dentro un archivo index.js. Ahí vas a simular tres tareas con setTimeout envueltas en promesas:
js
const tarea1 = () => new Promise((resolve) => {
setTimeout(() => resolve('tarea uno'), 1000);
});
const tarea2 = () => new Promise((resolve) => {
setTimeout(() => resolve('tarea dos'), 1500);
});
const tarea3 = () => new Promise((resolve) => {
setTimeout(() => resolve('tarea tres'), 2000);
});
Promise.all([tarea1(), tarea2(), tarea3()])
.then((resultado) => console.log(resultado))
.catch((error) => console.error(error));
Al ejecutar node index.js notarás una demora, pero todas las tareas se resuelven en paralelo. Cuando termina la última, se dispara la función con el arreglo de resultados.
¿Por qué no se bloquea el event loop?
Porque cada setTimeout es un proceso asíncrono manejado por el motor, no por tu código síncrono. El event loop queda libre para atender otras tareas mientras los temporizadores hacen lo suyo. Recuerda que los timers en JavaScript no son exactos: se ejecutan cuando el event loop está disponible [3:05].
Usar catch para controlar errores es buena práctica incluso si no haces reject explícito en ninguna tarea. Te protege ante fallos inesperados.
¿Cómo controlar flujos secuenciales con generators?
promise.all es ideal para paralelismo, pero ¿qué pasa cuando necesitas procesos secuenciales o iteradores infinitos sin bloquear el motor? Ahí entran los generators [4:25].
Un generator es una función que, mediante la palabra clave yield, devuelve un valor y pausa su ejecución hasta que la vuelves a llamar. Se define con function* y permite asincronía controlada.
¿Cuándo usar un generator en lugar de una promesa? Cuando necesitas producir valores bajo demanda, como en iteradores infinitos o secuencias largas, sin calcular todo de golpe ni bloquear el event loop.
Ejemplo práctico: serie Fibonacci con generators
Crea un archivo fibonacci.js y escribe un generator que calcule la serie Fibonacci de forma controlada:
js
function* fibonacci() {
let current = 0;
let next = 1;
while (true) {
yield current;
[current, next] = [next, current + next];
}
}
const gen = fibonacci();
for (let i = 0; i < 10; i++) {
console.log('dentro del ciclo:', gen.next().value);
}
console.log('fuera del ciclo:', gen.next().value);
console.log('fuera del ciclo:', gen.next().value);
Aunque tienes un while true, no se bloquea nada. Cada yield pausa la función hasta la siguiente llamada a gen.next(). Puedes generar 10 valores dentro del ciclo y seguir generando más fuera de él, porque el estado del generator persiste [6:30].
Un detalle importante: el yield necesita punto y coma al final de la línea siguiente para que el generator devuelva el valor correctamente. Si usas un linter como StandardJS que omite punto y coma, este es uno de los casos donde sí lo necesitas [7:45].
¿Qué estrategia asíncrona conviene en cada caso?
No todo se resuelve con promesas. Cada herramienta tiene su lugar y mezclarlas con criterio es lo que diferencia a un buen desarrollador de Node.
- Usa callbacks cuando la API que consumes los exige o cuando el flujo es simple y no amerita más abstracción.
- Usa promesas con
promise.all cuando tienes múltiples tareas independientes que pueden correr en paralelo.
- Usa generators cuando necesitas secuencias controladas, iteradores infinitos o pausar y reanudar lógica bajo demanda.
La regla de oro: siempre que puedas derivar un proceso a asincronía, hazlo. Bloquea el event loop solo cuando sea estrictamente necesario, porque ahí es donde Node.js pierde su ventaja competitiva.
¿Ya probaste combinar promise.all con generators en un proyecto real? Cuéntame en los comentarios qué tipo de tareas asíncronas manejas y qué estrategia te ha funcionado mejor.