В node.js нет ничего параллельного, так как он однопоточный.Однако несколько событий можно запланировать и запустить в последовательности, которую вы не можете определить заранее.А некоторые вещи, такие как доступ к базе данных, на самом деле «параллельны» в том смысле, что сами запросы к базе данных выполняются в отдельных потоках, но после завершения реинтегрируются в поток событий.
Итак, как запланировать обратный вызов для несколькихобработчики событий?Что ж, это один из распространенных методов, используемых в анимациях в браузере на стороне javascript: используйте переменную для отслеживания завершения.
Это звучит как хак, и это звучит потенциально беспорядочно, оставляя кучу глобальных переменных вокругделать отслеживание и на меньшем языке это было бы.Но в javascript мы можем использовать замыкания:
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);
}
}
// usage:
fork([A,B,C],D);
В приведенном выше примере мы сохраняем код простым, предполагая, что функции async и callback не требуют аргументов.Конечно, вы можете изменить код для передачи аргументов асинхронным функциям и заставить функцию обратного вызова накапливать результаты и передавать ее в функцию shared_callback.
Дополнительный ответ:
На самом деле, дажекак таковая, функция fork()
уже может передавать аргументы асинхронным функциям с помощью замыкания:
fork([
function(callback){ A(1,2,callback) },
function(callback){ B(1,callback) },
function(callback){ C(1,2,callback) }
],D);
остается только накопить результаты из A, B, C и передать ихк Д.
Еще более дополнительный ответ:
Я не удержался.Думал об этом во время завтрака.Вот реализация fork()
, которая накапливает результаты (обычно передаваемые в качестве аргументов функции обратного вызова):
function fork (async_calls, shared_callback) {
var counter = async_calls.length;
var all_results = [];
function makeCallback (index) {
return function () {
counter --;
var results = [];
// we use the arguments object here because some callbacks
// in Node pass in multiple arguments as result.
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));
}
}
Это было достаточно просто.Это делает fork()
достаточно универсальным и может использоваться для синхронизации нескольких неоднородных событий.
Пример использования в Node.js:
// Read 3 files in parallel and process them together:
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];
// process the files together here
}
fork([A,B,C],D);
Обновление
Этот код был написан до существования таких библиотек, как async.js или различных библиотек, основанных на обещаниях.Я хотел бы верить, что async.js был вдохновлен этим, но у меня нет никаких доказательств этого.Во всяком случае ... если вы думаете сделать это сегодня, посмотрите async.js или обещания.Просто рассмотрите приведенный выше ответ как хорошее объяснение / иллюстрацию того, как работают такие вещи, как async.parallel.
Для полноты картины, вот как вы это сделаете с async.parallel
:
var async = require('async');
async.parallel([A,B,C],D);
Обратите внимание, что async.parallel
работает точно так же, как и функция fork
, которую мы реализовали выше.Основное отличие состоит в том, что он передает ошибку в качестве первого аргумента D
и обратный вызов в качестве второго аргумента согласно соглашению node.js.
Используя обещания, мы напишем его следующим образом:
// Assuming A, B & C return a promise instead of accepting a callback
Promise.all([A,B,C]).then(D);