2007年 11月 30日

Deferred チェイン。

MochiKit の実装はもっとシンプルにできそうなのでつくってみた。でもいろいろ頭悪くて考えきれてないと思う……

例えばこういう風に書けるようにする。

wait(1).
next(function (e) {
	log([1, e]);
	return wait(1).next(function (e) {
		log([2, e]);
		return wait(2).next(function (e) {
			log([3, e]);
		});
	});
}).
next(function (e) {
	log(4);
	return "555";
}).
next(function (e) {
	log(e);
})

末尾再帰っぽいの (return call ってのが tailcall っぽくてよくないですか><)

next(function () {
	log("start");
}).
next(function () {
	function pow (x, n) {
		function _pow (n, r) {
			if (n == 0) return r;
			return call(_pow, n - 1, x * r);
		}
		return call(_pow, n, 1);
	}
	return call(pow, 2, 10);
}).
next(function (r) {
	log([r, "end"]);
}).
error(function (e) {
	alert(e);
})

実装

function Deferred () { this.init.apply(this) }
Deferred.prototype = {
	init : function () {
		this.callback = {
			ok: function (x) { return x },
			ng: function (x) { throw  x }
		};
		this._next    = null;
	},

	next  : function (fun) { return this._post("ok", fun); },
	error : function (fun) { return this._post("ng", fun); },
	call  : function (val) { return this._fire("ok", val); },
	fail  : function (err) { return this._fire("ng", err); },

	cancel : function () {
		this._next = null;
	},

	_post : function (okng, fun) {
		this.callback[okng] = fun;
		this._next = new Deferred();
		return this._next;
	},

	_fire : function (okng, value) {
		var self = this;
		var next = "ok";
		try {
			value = self.callback[okng].call(self, value);
		} catch (e) {
			next  = "ng";
			value = e;
		}
		if (value instanceof Deferred) {
			value._next = self._next;
		} else {
			setTimeout(function () {
				if (self._next) self._next._fire(next, value);
			}, 0);
		}
	}
};

function wait(n) {
	var d = new Deferred();
	var t = new Date();
	setTimeout(function () {
		// 実際にかかった時間をコールバック
		d.call((new Date).getTime() - t.getTime());
	}, n * 1000)
	return d;
}

function ps () {
	var d = new Deferred();
	setTimeout(function () { d.call() }, 0);
	return d;
}

function next (fun) {
	return ps().next(fun);
}

function call (f, args) {
	args = Array.prototype.slice.call(arguments);
	f    = args.shift();
	return next(function () {
		return f.apply(null, args);
	});
}

なかなかカッコいい感じがする。もうちょいいじる。

next は this ではなくて、次の Deferred (this が準備できたらよばれる Deferred // _next) をかえす。これでずっとチェインしていく。もしコールバックが Deferred をかえしたら、その Deferred に _next をわたしてあげる。_next は継続 (ていっていいのかな。継続をちゃんと理解していない)

スタック消耗してたのを修正した