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);
Uważam, że teraz moduł „async” zapewnia tę równoległą funkcjonalność i jest mniej więcej taki sam, jak powyższa funkcja fork.
źródło
fork
funkcjaFutures moduł ma submodule nazwie przyłączyć że mam ochotę użytku:
Plik Readme pokazuje kilka dobrych przykładów używania go w stylu dowolnym lub przyszłego modułu podrzędnego przy użyciu wzorca Promise. Przykład z dokumentów:
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);
źródło
Możliwe byłoby proste rozwiązanie tutaj: http://howtonode.org/control-flow-part-ii przewiń do Akcje równoległe. Innym sposobem byłoby, aby wszystkie A, B i C miały tę samą funkcję zwrotną, aby ta funkcja miała globalny lub przynajmniej poza funkcją inkrementor, jeśli wszystkie trzy wywoływały wywołanie zwrotne, pozwól mu działać D, oczywiście będziesz musiał także gdzieś przechowywać wyniki A, B i C.
źródło
Inną opcją mógłby być moduł Step dla Node: https://github.com/creationix/step
źródło
Możesz wypróbować tę małą bibliotekę: https://www.npmjs.com/package/parallel-io
źródło
Oprócz popularnych obietnic i biblioteki asynchronicznej istnieje trzeci elegancki sposób - użycie „okablowania”:
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
źródło