Hatena::ブログ(Diary)

ひだちのいろの日記 RSSフィード

2011/08/04

[]Javascript で複数のコールバックをまとめて受け取る

複数のコールバックをまとめて受け取るためのクラスを書いてみました。

JavaScript は何かとコールバックが多用される言語です。単発のコールバックならば問題はありませんが、複数のコールバックを同時に使用して、それらが全て呼ばれたときに処理を進めるなどの処理は記述が複雑になりがちです。

そこでこの Callback クラス。

var cb = new Callback();
request(..., cb.here());
request(..., cb.here());
request(..., cb.here());
cb.then = function(result1, result2, result3) {

}

上記のように「複数のコールバック呼び出しが完了した時点で処理をすすめる」パターンを簡素に書くことができます。

各コールバックに渡された引数もまとめて最後の then 関数に渡され、しかもその順番は cb.here を呼び出した順番と対応することが保証されます。(コールバックが呼び出された順ではありません)

これで複数リソースの読み込みも怖くない!

var Callback = (function () {

  /**
   * 複数のコールバックをまとめて一つのコールバックにします。
   * @constructor
   * @example
   * var cb = new Callback();
   * asyncFunc1(..., cb.here()); // f('guitar') とコールバックされる
   * asyncFunc2(..., cb.here()); // f('fiddle','banjo') とコールバックされる
   * asyncFunc3(..., cb.here()); // f()  とコールバックされる
   * asyncFunc4(..., cb.here([])); // f('mandolin') とコールバックされる
   * cb.then = function(a, b, c, d) {
   *   // cb.here() を呼んだ数だけ引数が渡る
   *   // a は 'guitar'
   *   // b は ['fiddle', 'banjo']
   *   // c は null
   *   // d は ['mandolin']
   * }
   */
  var Callback = function() {
    this.resultList = [];
    this.stepCount = 0;
    this.callCount = 0;
    /**
     * すべてのコールバックをまとめて最終的に呼び出される関数。
     */
    this.then = null;
  };
  
  /**
   * コールバック関数を生成して返す。
   * 生成した関数が空引数で呼ばれた場合は null を。
   * 1引数で呼ばれた場合はその値を。
   * 2引数以上で呼ばれた場合は配列を登録し、
   * すべてのコールバックが呼び出された時点で
   * 登録した引数を then に渡して呼び出す。
   * here に空配列を渡した場合は引数の数にかかわらず配列を登録する。
   * @return {function(...)}
   */
  Callback.prototype.here = function() {
    var registerArray = arguments.length == 1 &&
        arguments[0] instanceof Array &&
        arguments[0].length == 0;
    if (arguments.length != 0 && !registerArray) throw new Error(
        "Callback#here に引数が渡されました。" +
        "xx.here() と書くべき場所で xx.here としている可能性があります。");
    return (function(callback, stepCount) {
      return function() {
        var result = arguments;
        if (!registerArray && result.length == 0) {
          callback.resultList[stepCount] = null;
        } else if (!registerArray && result.length == 1) {
          callback.resultList[stepCount] = result[0];
        } else {
          callback.resultList[stepCount] = [];
          for (var i = 0; i<result.length; i++) {
            callback.resultList[stepCount][i] = result[i];
          }
        }
        if (++callback.callCount >= callback.stepCount) {
          if (callback.then != null) {
            callback.then.apply(null, callback.resultList);
            Callback.call(this);
          } else throw new Error("then が未登録です");
        }
      }
    })(this, this.stepCount++);
  };

  return Callback;
})();

コードはご自由にお使いください。

2010/09/01

[]JavaScript で親クラスのコンストラクタを呼ばずに継承を行う

JavaScriptprototype を使った継承のオーソドックスな形は以下のような感じだと思います。

function SuperClass(name) {
	this.name = name;
}
SuperClass.prototype.hello = function() { alert(this.name+" : hello!"); }

function ChildClass() {
	SuperClass.call(this, "Koyomi Araragi");
}
ChildClass.prototype = new SuperClass("");

new ChildClass().hello();

親クラスのprototypeにアクセスするために、ChildClass.prototype に親クラスのインスタンスを生成して代入しています。これにより ChildClass のインスタンスから親クラスの hello が呼べるようになります。

この方法だと、継承のたびに親クラスのコンストラクタを呼ばなければなりません。親クラスのコンストラクタが、無効な引数に対して例外を投げる設計だったり、問題となる副作用を持っていたりすると厄介です。

以下のようにすると、親クラスのコンストラクタを呼ばずに継承を行えます。

function createObjectForPrototype(type) {
	var f = function(){};
	f.prototype = type.prototype;
	return new f();
}

function SuperClass(name) {
	this.name = name;
}
SuperClass.prototype.hello = function() { alert(this.name+" : hello!"); }

function ChildClass() {
	SuperClass.call(this, "Koyomi Araragi");
}
ChildClass.prototype = createObjectForPrototype(SuperClass);

new ChildClass().hello();

2009/11/03

[][]新しい書き方

ECMAScript (JavaScript, ActionScript など) では new の対象に関数を持ってくることができる.

function foo() {}
new foo();

この関数コンストラクタ関数とよび、ふつうはコンストラクタ関数内で this を使って生成されるオブジェクトにアクセスする.

ところがコンストラクタ関数内で新しくオブジェクトを作って返してやると、それが new で生成されるオブジェクトになるらしい.

function foo() {
    var a={};
    return a;
}

// ↓これで返ってくるのは a だよ!
new foo();

この a を関数とすることもできる.また、コンストラクタ関数内で a にプロパティを設定することもできる*1ので、

  1. ふつうにメソッドやプロパティを持つ
  2. しかも関数として呼べる

不思議なオブジェクトを new で生成することが可能だ.

function foo() {
    var a = function(){ return "test"; };
    a.say = function(){ return "hello"; };
    return a;
}

a.say() // hello
a();    // test

これをメソッドチェーンの形と混ぜてやると以下のような書き方が合法になる.

a = new foo(); // <-- 関数としても呼べるオブジェクト
a.foo(5)
 .bar(4)
 (2)
 .foo(5);

これでなにかおもしろいライブラリが作れないか考え中.

*1:ただし ActionScript の場合は変数の型宣言を * にするのを忘れずに