たごもりすメモ

コードとかその他の話とか。

node.jsで複数の処理を並列に実行して全部完了したらコールバックを呼び出したい

I/Oを含む処理はnode.jsにおいてコールバックを繋げる一連の非同期処理として実装するんだけど、これらの一連の処理を複数並列に実行し、それら複数の処理が全部完了したら特定のひとつのコールバックを呼び出す、というようにしたいなー。
と思ってうっかり自分で書いてしまったあとに世の中の実装を調べたんだけど、どうも既存の実装は無いっぽい? のかな? 誰か知ってたら教えてください。というのがこのエントリの主旨です。w

(13:08 追記) sugyan が既に書いていたよ! ありがとうありがとうsugyan!

async.jsでフロー制御 - すぎゃーんメモ
リンク先の async.parallel がまさにそのものでしたので読みましょう。このエントリの以下の部分を読む必要は全くありません。

ていうか、async.jsとasyncって別々のモジュールなのね。ひどい。

async.js

JSDeferredは見るからにコールバックチェーンを繋げるだけで並列実行とかしてくれなさそうだったので async.js を試してみた。結論から言うと、こいつも並列実行はしてくれなかった。

npmでインストールできる。

var async = require('asyncjs');
var sum = 0;
async.list([
  function a(next){ setTimeout(function(){console.log("in a with " + sum); sum += 1; next(null);},500); },
  function b(next){ console.log("in b with " + sum); sum -= 1; next(null); }
]).call().end(function(err,data){ console.log("end with " + sum); })

これを実行すると、以下のような出力になる。

in a with 0
in b with 1
end with 0

きっちり a を実行してから b の実行に入ってるね。残念。async.jsと言いながら、どっちかっつーと非同期処理を同期的に実行するためのライブラリと考えた方がよさそう。

書いたやつ

思わず書いてしまったのはこんな感じ。ライブラリ呼び出しの形式に直してないんだけど、まあだいたい思った通りに動いた。

var complete_callback = function(list, callback){
  var waiting_callbacks = list;
  var errors = [];
  var position = {};
  waiting_callbacks.forEach(function(k){position[k] = false;});
  return function(type, err){
    if (err)
      errors.push(err);
    position[type] = true;
    for(var i = 0; i < waiting_callbacks.length; i++) {
      if (! position[waiting_callbacks[i]])
        return;
    }
    if (errors.length > 0)
      callback(errors);
    else
      callback();
  };
};

var test_complete_callback = complete_callback(['a','b'], function(errors){ console.log("end: " + sum); });
var sum = 0;
var a = function(){
  setTimeout(function(){
    console.log("in a with " + sum);
    sum += 1;
    test_complete_callback('a');
  }, 500);
};
var b = function(){console.log("in b with " + sum); sum -= 1; test_complete_callback('b');};
a(); b();

出力はこうなる。

in b with 0
in a with -1
end: 0

a の完了を待たずに b を実行し、最終的なコールバックは a/b 両方が完了してから呼び出されてる。ばっちり!

ライブラリ化するとしたら以下のような呼び出し形式にできるかなーとは思ってる。

concurrent.call({
  a: function(){
    setTimeout(function(){
      console.log("in a with " + sum);
      sum += 1;
      end('a');
    }, 500);
  },
  b: function(){
    console.log("in b with " + sum);
    sum -= 1;
    end('b');
  }
}, function(errors){ console.log("end with " + sum); })

さて、ちゃんとまとめるかどうするか……。自分でも使いそうかどうかよくわかってないぞ。