No description, website, or topics provided.
JavaScript
Switch branches/tags
Nothing to show
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
README.mkd
deferred.js
test.html
trial.html

README.mkd

deferred-generator

JavaScript 1.7のジェネレータを使った一風変わったDeferredライブラリ。クロスブラウザには対応してません!

harmonyのawait式がアイディアのベースになってます

サンプル

基本

var main = Deferred.generator(function () {
	for (var i = 0; i < 3; i ++) {
		console.log(i);
		yield Deferred.wait(0.1);
	}
});

main().then(function () {
	console.log("done");
});

// 0, 1, 2, done と時間をおいて出力されます

ちなみに上でmainに代入された関数はだいたい次と同じようなことをします

function main() {
	var deferred = new Deferred;
	function loop(i) {
		if (i >= 3) {
			deferred.callback();
		} else {
			console.log(i);
			Deferred.wait(0.1).then(loop.bind(null, i + 1));
		}
	}
	loop(0);
	return deferred;
}

キャンセル

var main = Deferred.generator(function () {
	for (var i = 0; true; i ++) {
		console.log(i);
		yield Deferred.wait(0);
	}
});
var deferred = main();
setTimeout(function () {
	deferred.cancel();
}, 1000);

// 0, 1, 2, 3 ... と次々に出力され、1秒たつと終了します

値を返す

// 0.1秒待って引数の2倍の値を返す
var f = Deferred.generator(function (x) {
	yield Deferred.wait(0.1);
	yield Deferred.returnValue(x * 2);
});
var main = Deferred.generator(function () {
	for (var i = 0; i <= 4; i ++) {
		var result = yield f(i);
		console.log(result);
	}
});
main();

// 0.1秒ごとに 0, 2, 4, 6, 8 と出力されます

エラー処理

var f = Deferred.generator(function () {
	yield Deferred.wait(0.1);
	throw "hoge";
});
var main = Deferred.generator(function () {
	try {
		yield f();
	} catch (e) {
		console.log(e); //=> "hoge"
	}
});
main();

再帰

再帰クイックソートの可視化: Days on the Moonのコードを元にしています

var quickSort = Deferred.generator(function (data, begin, end, log) {
	if (begin >= end) return;
	var pivotPos = partition(data, begin, end);
	yield log(data);
	yield quickSort(data, begin, pivotPos, log);
	yield quickSort(data, pivotPos + 1, end, log);
});

function partition(data, begin, end) {
	var pivotPos = begin;
	var pivot = data[pivotPos];

	for (var i = begin + 1; i < end; i++) {
		if (data[i] < pivot) {
			var temp = data[i];
			data[i] = data[pivotPos + 1];
			data[pivotPos + 1] = data[pivotPos];
			data[pivotPos] = temp;
			pivotPos++;
		}
	}
	return pivotPos;
}

var printSleep = Deferred.generator(function (x) {
	console.log(x);
	yield Deferred.wait(0.1);
});

var array = [9, 5, 7, 3, 2, 4, 0, 1, 6, 8];
quickSort(array, 0, array.length, printSleep).then(function () {
	console.log("done");
});

注意点

Deferred.generator()を除けばこれはとても低機能です。 JSDeferredのnextや、MochiKit.Async.DeferredのaddCallbackのような使い方はできません。

// thenをメソッドチェーンしたり
deferred.then(function () {
	...
}.then(function () {
	...
});

// thenを複数回書いたり
deferred.then(function () { ... });
deferred.then(function () { ... });

JSDeferredでいうところのchild DeferredやMochiKit.Async.Deferredでいうところのchained Deferredのような機能もありません。

また、非同期処理をまったく行わないDeferredでも問題なく動くようにDeferred#callbackは必ずsetTimeoutで遅延してから設定されたコールバックを呼びます。

var main = Deferred.generator(function () {
	yield Deferred.returnValue(42);
});
/* または
function main() {
	var deferred = new Deferred;
	deferred.callback(42);
	return deferred;
}
*/

main().then(function (x) {
	console.log(x);
});

MochiKit.Async.Deferredがやっているように既にcallback()で着火済みのDeferredにコールバック関数を設定すると同期的に呼ぶという方法もありましたが、実装が複雑になるので避けました。

こだわったところ

  • 実装はシンプルに
  • キャンセル機能
  • 捕捉されなかったエラーは握りつぶさずに再スロー