JavaScriptのジェネレータとDeferredを組み合わせるといい感じ! (JS1.7のyieldでharmonyのawait式ぽいことをする)

repl.it経由でtraceur-compilerを知った。そしてharmonyにはawait式というものがあるということを知った。

function deferredTimeout(delay) {
    var deferred = new Deferred();
    setTimeout(function() {
        deferred.callback({
            called: true
        })
    },
    delay);
    return deferred;
}

function a() {
    for (var i = 0; i < 4; ++i) {
        console.log("a"+i);
        await deferredTimeout(1000);
    }
}

function b() {
    for (var i = 0; i < 4; ++i) {
        console.log("b"+i);
        await deferredTimeout(1000);
    }
}

function main() {
	await a();
	await b();
}

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

ほほう。
で、これJS1.7のyieldでだいたい同じことできるよねって思ったので試した。

function deferredTimeout(delay) {
    var deferred = new Deferred();
    window.setTimeout(function() {
        deferred.callback({});
    },
    delay);
    return deferred;
}

function generatorToDeferred(func) {
	return function () {
		var deferred = new Deferred();
		var generator = func.apply(this, arguments);
		function loop(val) {
			try {
				var waitTask = generator.send(val);
				waitTask.then(loop);
			} catch (e if e instanceof StopIteration) {
				deferred.callback();
			}
		}
		loop(undefined);
		return deferred;
	}
}

var a = generatorToDeferred(function () {
	for (var i = 0; i < 4; ++i) {
		console.log("a"+i);
		yield deferredTimeout(100);
	}
});

var b = generatorToDeferred(function () {
	for (var i = 0; i < 4; ++i) {
		console.log("b"+i);
		yield deferredTimeout(100);
	}
});

var main = generatorToDeferred(function () {
	yield a();
	yield b();
});


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

おお、これはいいんじゃないか?

  • JavaScriptのジェネレータについて思うこと - fujidigの雑記で考えた「ジェネレータで非同期処理が同期的に書けるいうてもfor (var v in nantoka()) yield v;とかしないといけないやん」って問題がyield nantoka();で済むようになってる
  • waitTaskのエラーを戻り値のdeferredに伝搬させる処理は実装してないけどすぐ実装できるはず
  • deferredのcancellerでwaitTaskのcancelを呼ぶようにしたら現在アイドル中のdeferredがキャンセルできるはず!
  • そうすればMochiKitのDeferredのような複雑なDeferredライブラリを使わなくてよくなりそう!チェイン機能のないDeferredライブラリでOKなんじゃないだろか

で懸念点。generatorToDeferredに渡す関数内でreturnしたらその値がdeferred.callbackに渡ってほしいんだけど、ジェネレーターはStopIterationオブジェクトからreturnされた値がとれるのかと思いきやどうやらそういうのはなさそうだ...?んーreturnではなく終了することを表す特別な値をyieldするという方法で代替できるけど...
っていうかジェネレーターの関数でreturnに戻り値を書いてたらパース時にエラーになるね...

function f() { yield 1; return 2; } // TypeError: generator function f returns a value

リンク