Nada es realmente paralelo en node.js, ya que es de un solo subproceso. Sin embargo, se pueden programar y ejecutar varios eventos en una secuencia que no se puede determinar de antemano. Y algunas cosas, como el acceso a la base de datos, son en realidad "paralelas" en el sentido de que las consultas de la base de datos se ejecutan en subprocesos separados pero se vuelven a integrar en el flujo de eventos cuando se completan.
Entonces, ¿cómo se programa una devolución de llamada en varios controladores de eventos? Bueno, esta es una técnica común usada en animaciones en javascript del lado del navegador: use una variable para rastrear la finalización.
Esto suena como un truco y lo es, y suena potencialmente desordenado dejando un montón de variables globales alrededor del seguimiento y en un lenguaje menor sería. Pero en javascript podemos usar cierres:
function fork (async_calls, shared_callback) {
var counter = async_calls.length;
var callback = function () {
counter --;
if (counter == 0) {
shared_callback()
}
}
for (var i=0;i<async_calls.length;i++) {
async_calls[i](callback);
}
}
fork([A,B,C],D);
En el ejemplo anterior, mantenemos el código simple asumiendo que las funciones async y callback no requieren argumentos. Por supuesto, puede modificar el código para pasar argumentos a las funciones asíncronas y hacer que la función de devolución de llamada acumule resultados y los pase a la función de devolución de llamada compartida.
Respuesta adicional:
En realidad, incluso tal como está, esa fork()
función ya puede pasar argumentos a las funciones asíncronas usando un cierre:
fork([
function(callback){ A(1,2,callback) },
function(callback){ B(1,callback) },
function(callback){ C(1,2,callback) }
],D);
lo único que queda por hacer es acumular los resultados de A, B, C y pasarlos a D.
Aún más respuesta adicional:
No pude resistir. Seguí pensando en esto durante el desayuno. Aquí hay una implementación de fork()
que acumula resultados (generalmente pasados como argumentos a la función de devolución de llamada):
function fork (async_calls, shared_callback) {
var counter = async_calls.length;
var all_results = [];
function makeCallback (index) {
return function () {
counter --;
var results = [];
for (var i=0;i<arguments.length;i++) {
results.push(arguments[i]);
}
all_results[index] = results;
if (counter == 0) {
shared_callback(all_results);
}
}
}
for (var i=0;i<async_calls.length;i++) {
async_calls[i](makeCallback(i));
}
}
Eso fue bastante fácil. Esto tiene fork()
un propósito bastante general y se puede utilizar para sincronizar múltiples eventos no homogéneos.
Ejemplo de uso en Node.js:
function A (c){ fs.readFile('file1',c) };
function B (c){ fs.readFile('file2',c) };
function C (c){ fs.readFile('file3',c) };
function D (result) {
file1data = result[0][1];
file2data = result[1][1];
file3data = result[2][1];
}
fork([A,B,C],D);
Actualizar
Este código fue escrito antes de la existencia de bibliotecas como async.js o las diversas bibliotecas basadas en promesas. Me gustaría creer que async.js se inspiró en esto, pero no tengo ninguna prueba de ello. De todos modos ... si estás pensando en hacer esto hoy, echa un vistazo a async.js o promesas. Solo considere la respuesta anterior como una buena explicación / ilustración de cómo funcionan cosas como async.parallel.
En aras de la integridad, lo siguiente es cómo lo haría con async.parallel
:
var async = require('async');
async.parallel([A,B,C],D);
Tenga en cuenta que async.parallel
funciona exactamente igual que la fork
función que implementamos anteriormente. La principal diferencia es que pasa un error como primer argumento D
y la devolución de llamada como segundo argumento según la convención de node.js.
Usando promesas, lo escribiríamos de la siguiente manera:
Promise.all([A,B,C]).then(D);