Nic nie jest naprawdę równoległe w node.js, ponieważ jest on jednowątkowy. Jednak wiele wydarzeń można zaplanować i uruchomić w kolejności, której nie można wcześniej określić. Niektóre rzeczy, takie jak dostęp do bazy danych, są w rzeczywistości „równoległe”, ponieważ same zapytania do bazy danych są uruchamiane w oddzielnych wątkach, ale po zakończeniu są ponownie integrowane ze strumieniem zdarzeń.
Jak więc zaplanować wywołanie zwrotne w wielu programach obsługi zdarzeń? Cóż, jest to jedna z powszechnych technik używanych w animacjach w javascript po stronie przeglądarki: użyj zmiennej do śledzenia ukończenia.
Brzmi to jak hack i tak właśnie jest, i brzmi potencjalnie niechlujnie, pozostawiając kilka zmiennych globalnych wokół wykonywania śledzenia i w mniejszym języku. Ale w javascript możemy użyć domknięć:
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);
W powyższym przykładzie utrzymujemy kod w prostocie, zakładając, że funkcje asynchroniczne i wywołania zwrotne nie wymagają argumentów. Możesz oczywiście zmodyfikować kod tak, aby przekazywał argumenty do funkcji asynchronicznych, a funkcja wywołania zwrotnego gromadzi wyniki i przekazuje je do funkcji shared_callback.
Dodatkowa odpowiedź:
Właściwie, nawet jeśli jest, ta fork()
funkcja może już przekazywać argumenty do funkcji asynchronicznych za pomocą zamknięcia:
fork([
function(callback){ A(1,2,callback) },
function(callback){ B(1,callback) },
function(callback){ C(1,2,callback) }
],D);
pozostaje tylko zebrać wyniki z punktów A, B, C i przekazać je do D.
Jeszcze bardziej dodatkowa odpowiedź:
Nie mogłem się oprzeć. Myślałem o tym podczas śniadania. Oto implementacja, fork()
która gromadzi wyniki (zwykle przekazywane jako argumenty do funkcji zwrotnej):
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));
}
}
To było dość łatwe. Ma to fork()
dość ogólny cel i może być używane do synchronizowania wielu niejednorodnych zdarzeń.
Przykładowe użycie w 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);
Aktualizacja
Ten kod został napisany przed istnieniem bibliotek, takich jak async.js lub różne biblioteki oparte na obietnicach. Chciałbym wierzyć, że async.js został zainspirowany tym, ale nie mam na to żadnego dowodu. W każdym razie ... jeśli myślisz o zrobieniu tego dzisiaj, spójrz na async.js lub obietnice. Po prostu rozważ powyższą odpowiedź jako dobre wyjaśnienie / ilustrację tego, jak działają rzeczy takie jak asynchroniczne równoległe.
Ze względu na kompletność poniżej przedstawiono, jak to zrobić z async.parallel
:
var async = require('async');
async.parallel([A,B,C],D);
Zauważ, że async.parallel
działa dokładnie tak samo, jak fork
funkcja, którą zaimplementowaliśmy powyżej. Główna różnica polega na tym, że przekazuje błąd jako pierwszy argument, D
a wywołanie zwrotne jako drugi argument zgodnie z konwencją node.js.
Korzystając z obietnic, napisalibyśmy to w następujący sposób:
Promise.all([A,B,C]).then(D);