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);
Creo que ahora el módulo "async" proporciona esta funcionalidad paralela y es aproximadamente la misma que la función fork anterior.
fuente
fork
función anteriorEl módulo de futuros tiene un submódulo llamado unión que me ha gustado usar:
El archivo Léame muestra algunos buenos ejemplos de cómo usarlo en estilo libre o usar el submódulo futuro usando el patrón Promise. Ejemplo de los documentos:
var Join = require('join') , join = Join() , callbackA = join.add() , callbackB = join.add() , callbackC = join.add(); function abcComplete(aArgs, bArgs, cArgs) { console.log(aArgs[1] + bArgs[1] + cArgs[1]); } setTimeout(function () { callbackA(null, 'Hello'); }, 300); setTimeout(function () { callbackB(null, 'World'); }, 500); setTimeout(function () { callbackC(null, '!'); }, 400); // this must be called after all join.when(abcComplete);
fuente
Una solución simple podría ser posible aquí: http://howtonode.org/control-flow-part-ii desplácese hasta Acciones paralelas. Otra forma sería hacer que A, B y C compartan la misma función de devolución de llamada, que la función tenga un incremento global o al menos fuera de la función, si los tres han llamado a la devolución de llamada, déjela ejecutar D, por supuesto, también tendrá que almacenar los resultados de A, B y C en algún lugar.
fuente
Otra opción podría ser el módulo Step para Node: https://github.com/creationix/step
fuente
Es posible que desee probar esta pequeña biblioteca: https://www.npmjs.com/package/parallel-io
fuente
Además de las promesas populares y la biblioteca asíncrona, existe una tercera forma elegante: usar "cableado":
var l = new Wire(); funcA(l.branch('post')); funcB(l.branch('comments')); funcC(l.branch('links')); l.success(function(results) { // result will be object with results: // { post: ..., comments: ..., links: ...} });
https://github.com/garmoshka-mo/mo-wire
fuente