79

node.js のイベント駆動型プログラミング モデルでは、プログラム フローを調整するのが少し難しくなります。

単純な順次実行はネストされたコールバックに変換されますが、これは十分に簡単です (書き留めるのは少し複雑ですが)。

しかし、並列実行はどうでしょうか。並行して実行できる 3 つのタスク A、B、C があり、それらが完了したら、その結果をタスク D に送信したいとします。

fork/join モデルの場合、これは次のようになります。

  • フォークA
  • フォークB
  • フォーク C
  • A、B、Cに参加し、Dを実行

それを node.js にどのように書くのですか? ベストプラクティスやクックブックはありますか? 毎回ソリューションを手でロールする必要がありますか、それともヘルパーを備えたライブラリがありますか?

4

7 に答える 7

128

node.js はシングル スレッドであるため、真の並列性はありません。ただし、事前に決定できない順序で複数のイベントをスケジュールして実行することができます。また、データベース アクセスなどの一部は、データベース クエリ自体が個別のスレッドで実行されますが、完了するとイベント ストリームに再統合されるという点で、実際には「並列」です。

では、複数のイベント ハンドラでコールバックをスケジュールするにはどうすればよいでしょうか。これは、ブラウザー側の JavaScript のアニメーションで使用される一般的な手法の 1 つです。変数を使用して完了を追跡します。

これはハックのように聞こえますが、ハックのように聞こえます。追跡を行う際に大量のグローバル変数を残しておくと、厄介な言語になる可能性があります。しかし、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 からの結果を蓄積して D に渡すだけです。


さらに追加の答え:

私は抵抗できませんでした。朝食の間、これについて考え続けました。結果を蓄積する の実装を次に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 やさまざまな promise ベースのライブラリなどのライブラリが存在する前に作成されました。async.js はこれに触発されたと信じたいのですが、その証拠はありません。とにかく..今日これを行うことを考えているなら、async.jsまたはpromiseを見てください。上記の回答を、async.parallel などの機能の適切な説明/図と考えてください。

完全を期すために、次のようにしますasync.parallel

var async = require('async');

async.parallel([A,B,C],D);

上記で実装async.parallelした関数とまったく同じように機能することに注意してください。主な違いは、node.js の規則に従ってfork、最初の引数としてエラーを渡し、 2 番目の引数としてコールバックを渡すことです。D

promise を使用して、次のように記述します。

// Assuming A, B & C return a promise instead of accepting a callback

Promise.all([A,B,C]).then(D);
于 2011-01-08T01:54:29.867 に答える
10

「async」モジュールがこの並列機能を提供し、上記の fork 関数とほぼ同じになると思います。

于 2011-09-24T05:23:11.073 に答える
5

futuresモジュールには、私が好んで使用する joinというサブモジュールがあります。

pthread_joinスレッドの場合と同様に、非同期呼び出しを結合します。

readme には、フリースタイルで使用したり、Promise パターンを使用して将来のサブモジュールを使用したりする良い例がいくつか示されています。ドキュメントの例:

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);
于 2012-02-20T04:47:09.433 に答える
2

もう 1 つのオプションは、Node の Step モジュールです: https://github.com/creationix/step

于 2011-04-30T00:27:38.320 に答える
2

ここで簡単な解決策が可能かもしれません: http://howtonode.org/control-flow-part-ii並列アクションまでスクロールします。もう 1 つの方法は、A、B、および C で同じコールバック関数を共有し、その関数にグローバルまたは少なくとも関数外のインクリメンタを持たせ、3 つすべてがコールバックを呼び出してから D を実行させることです。もちろん、A、B、および C の結果もどこかに保存する必要があります。

于 2011-01-08T01:41:15.150 に答える
0

この小さなライブラリを試してみてください: https://www.npmjs.com/package/parallel-io

于 2014-12-22T20:45:04.737 に答える
0

一般的な promise と async-library に加えて、「配線」を使用する 3 番目のエレガントな方法があります。

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

于 2015-10-19T15:45:44.970 に答える