cf. q http://documentup.com/kriskowal/q/
JavaScriptは非同期APIの塊だ。node.jsはもっと然ふだ。非同期を上手く扱へないといけない。方法としては幾つか有る。callback関数をnestさせて頑張る。遅延評価のAPIに変換する。Deferred (Promise, Future) を使ふ。Flow.jsを使ふ。
cf. 最小限の非同期処理コントローラJavaScript http://c4se.hatenablog.com/entry/2013/06/14/195740
node.jsの次versionには、yieldが入るらしい。何年も前にFirefox, Rhinoに実装され、漸く最近ES6に入り、目出度くV8に実装されたものだ。yieldは、C#のasync - awaitの様に使へる。詰まり組み込みのdeferredであり、此れが入れば解決だが、未だ無い。Deferredのlibraryを使おう。
今迄client side (Web browser) で、jQuery.Deferredを使ってきてゐたが、今回node.jsを弄る事に成り、deferredを使ふ事にした。因みにjQuery.Deferredの移植も有るが。
ところが此のdeferred module、documentが詳しいくせに、明解とは言ひ難い。reduceを使って自動で連鎖させやうとしたが、少し困って了った。
前提
test.jsと云ふfileが有り、start(number,function())
と云ふ非同期関数がAPIとして公開されてゐるとする。例へば以下の実装だ。
// test.js 'use strict'; /** * @param {number} n * @param {function()} fn */ function start(n, fn) { setTimeout(function() { console.log(n); fn(); }, 1000); } module.exports = {start: start};
此の関数を、1, 2, 3と云ふ引数を与えて、「順番に」実行したいとしやう。普通にやると以下の様に成る。
// index.js var start = require('./test.js').start; start(1, function() { start(2, function() { start(3); }); });
人間の頭では追へない。うっかり文法errorとか起こしそうだ。
deferredを普通に使ふ
其所でdeferredを使おう。startをdeferred用の関数でくるんで、doStart(number)
としてやる。
// index.js var deferred = require('deferred'), start = require('./test.js').start; /** * @param {number} n * @return {deferred.promise} */ function doStart(n) { var d = deferred(); start(n, d.resolve); return d.promise; } // Some code using deferred here.
此のdoStartを、普通に使ふと、此う成る筈だ。
// Some code using deferred. deferred(start(1))( function() { return doStart(2); } )( function() { return doStart(3); } ).done();
functionと打つのが長ったらしいので、Function.prototype.bindを使っても好い。
// Some code using deferred. deferred(start(1))( doStart.bind(null, 2) )( doStart.bind(null, 3) ).done();
JavaScriptはmacroが無い丈のLispだ。みんな此んなにLispを使ってゐる。勝利だ。
此のdeferredを自動で連鎖させる
さて、此所からが本題だが、1, 2, 3と順番に実行する事が判ってゐるのだから、配列を回して自動で書いて了ひたいものだ。ところが上記を見ると解るが、最初の呼び出し丈、形式が違ふ。deferred(1の呼び出し)(2の予約)(3の予約)
と成ってゐる。此れが邪魔だ。思い切ってdeferred()(1の予約)(2の予約)(3の予約)
としてもerrorと成るし、deferred(1の予約)(2の予約)(3の予約)
としてやると、1は実行されない。
実はdeferred()にnullを渡してやれば好い。
// Some code using deferred. deferred(null)( doStart.bind(null, 1) )( dpStart.bind(null, 2) )( doStart.bind(null, 3) ).done();
deferred(null)(1の予約)(2の予約)(3の予約)
と云ふ形式に出来る。後は自動で連鎖させるのは簡単だ。
// Some code using deferred. [1, 2, 3].reduce(function(d, n) { return d(doStart.bind(null, n)); }, deferred(null));
以上。