m.deferred
これは、Mithrilの低レベルメソッドです。これはthenable APIのカスタム版です。
deferredは非同時性モナドです。deferredは、コールバックをバインドして、グラフ構造の計算木を構築するのに使えるpromise
プロパティを公開しています。
deferredは、resolve
もしくはreject
を呼び出すことで、値を適用することができます。その値は計算木に渡って処理されていきます。
それぞれの計算関数はその値を引数として受け取り、他の値を返すことが期待されています。その値は、木の中の次の計算関数に渡されていきます。
m.deferred
関数が返すdeferredオブジェクトは、resolve
とreject
の2つのメソッドと、promise
と呼ばれるプロパティを持ちます。これらのメソッドを呼ぶと、promise木に値を送信することができます。promise
プロパティは、promise木のルートです。このプロパティは、successCallback
コールバックと、errorCallback
コールバックを受け取る、then
メソッドを持っています。then
メソッドを呼ぶと、 successCallback
と、 errorCallback
で表される計算表現をpromiseに追加します。これは、resolve
かreject
が呼ばれると呼び出されます。then
メソッドは子供のpromiseを返します。この子のpromiseにもさらに子のpromiseを追加することができます。
promise
は普通の関数です。具体的にはm.prop
getter-setterで、promiseが成功すると、successCallback
が返す値が格納されます。
Mithrilのプロミスは自動的に自動再描画システムと統合されることはありません。もし、jQuery.ajax
)などのサードパーティの非同期ライブラリと一緒に使って、リクエストの完了時に再描画をさせたい時はm.startComputation
/ m.endComputation
を使用してください。
使用方法
//単体での利用
var greetAsync = function() {
var deferred = m.deferred();
setTimeout(function() {
deferred.resolve("hello");
}, 1000);
return deferred.promise;
};
greetAsync()
.then(function(value) {return value + " world"})
.then(function(value) {console.log(value)}); //1秒後に"hello world"と表示
getter-setter APIを使ったデータの取得
promiseオブジェクトは実際のgetter-setter関数であるため、promiseが果たされると、値が格納されます。
//非同期サービス
var greetAsync = function() {
var deferred = m.deferred();
setTimeout(function() {
deferred.resolve("hello");
}, 1000);
return deferred.promise;
};
//非同期の消費者
var greeting = greetAsync()
var processed = greeting.then(function(value) {return value + " world"})
console.log(greeting()) // undefined - `deferred.resolve`がまだ呼ばれてない
setTimeout(function() {
//ここで `deferred.resolve` が呼ばれた
console.log(greeting()) // "hello"
console.log(processed()) // "hello world"
}, 2000)
Mithrilの再描画システムへの統合
デフォルトでは、promiseはMithrilの自動再描画システムとは統合しません。非同期関数を使用して、その非同期で取得してきた値をビューに反映させたい時は、[m.startComputation
/ m.endComputation
]を呼ぶ必要があります。
//非同期サービス
var greetAsync = function() {
//Mithrilにサービス呼び出しが終わるまで再描画を待つように伝える
m.startComputation();
var deferred = m.deferred();
setTimeout(function() {
deferred.resolve("hello");
//サービスが完了したら、Mithrilに再描画するように伝える
m.endComputation();
}, 1000);
return deferred.promise;
};
非同期のコールバックを待たずに再描画を行っても問題ないケースもあります。その場合は、単にm.startComputation/m.endComputation呼び出しを省略します。
再描画の操作によっては、呼び出し前と後の両方に再描画が必要になるでしょう。この場合は、m.startComputation/m.endComputationを呼ぶ代わりに、m.redraw
を呼び出してください。
//非同期サービス
var greetAsync = function() {
//サービスを待たずに即座に再描画する
var deferred = m.deferred();
setTimeout(function() {
deferred.resolve("hello");
//再び再描画する
m.redraw()
}, 1000);
return deferred.promise;
};
Promises/A+との違い
ほとんどの部分で、MithrilのpromiseはPromise/A+の仕様通りの動作をしますが1つ異なる点があります。Mithrilのpromiseは、可能であれば同期で処理を行います。
同期実行
可能であればMithrilのpromiseは同期実行しようとします。下記のコードを使用して、MithrilとA+ promisesの違いを紹介します:
var deferred = m.deferred()
deferred.promise.then(function() {
console.log(1)
})
deferred.resolve("value")
console.log(2)
このサンプルを実行すると、A+ promisesの場合は、 1
の前に2
をログ出力しますが、Mithrilは2
の前に1
を表示します。一般的には、resolve
/reject
はthen
メソッドが呼ばれた後に非同期で呼ばれます。一般的にはこの差はそれほど大きな問題ではありません。
Mithrilがコールバックを同期で実行しているにはいくつか理由があります。仕様を満たすには、setImmediate
ポリフィル(将来のバージョンのJavaScriptと互換性を持たせるための、非常に巨大なライブラリ)か、setTimeout
(仕様により、最低でも4ミリ秒の待ちが発生する)が要求されます。Mithrilは敏捷性とパフォーマンスにフォーカスしているため、これらのトレードオフを受け入れることはできませんでした。
チェックしていないエラーのハンドリング
デフォルトでは、MithrilはエラーがErrorクラスのサブクラスであれば、必ずフレームワーク外までそれを届けます。Promises/A+の仕様通りであれば、例外クラスそのもののインスタンス、もしくは何らかのプリミティブなオブジェクトを手動で投げるとrejectionコールバックが呼ばれます。
仕様と異なるこの動作により、タイプミスによるnull参照例外などの一般的なエラーを開発者が簡単に見つけることができます。デフォルトでは、Promise/A+の仕様ではすべての例外はrejectionを起動しますが、開発者がこの失敗時のコールバックを設定し忘れると、誰もエラーを受け取らずに例外は握りつぶされます。
開発者は、最初にその存在を把握しなければ、nullになる可能性のあるオブジェクトのプロパティのアクセスエラーのハンドリングをしたいとは思わないでしょう。このような問題を防ぐ唯一の方法は、null参照例外が発生する可能性のあるすべての箇所に存在チェックを追加する方法しかありません。このようなエラーに対しては、例外が発生したときにエラーメッセージと行番号をコンソールに表示する方が開発者にやさしいでしょう。
m.request({method: "GET", url: "/things"})
.then(function(items) {
item.foreach(doSomething) //プログラマのエラー: タイプミスによって実行時エラーがログに出力
})
もちろん、コインの反対側の標準のPromise/A+の仕様もサポートされています。バリデーションの失敗があってエラーメッセージを表示しなければならない場合など、開発者がpromiseのコールバックの中で例外の発生を知らせなければならない場合は、new Error
で作成した例外を投げて、それを受け取ることができます。
var error = m.prop()
m.request({method: "GET", url: "/user/:id", data: {id: 1}})
.then(function(user) {
if (!user.isAdmin) throw new Error("もうしわけありません。権限がありません。")
})
.then(null, error) //アプリケーションのエラーをテンプレートで表示するためにsetter-getterにバインド
このように、デフォルトの例外処理のセマンティクスが変更されています。次のセクションもご覧ください。
例外モニタ
promiseコールバックの中で例外が発生すると、Mithrilはm.deferred.onerror(e)
を呼びます。
デフォルトでは、このイベントハンドラはこのエラーがErrorのサブクラスの例外で、Errorそのもののインスタンスでなければコンソールにその例外を出力します。そうでない場合は、Promises/A+の仕様に従います。Mithrilがこのような動作をするのは、多くのプログラマにとって、null参照例外などの未知の例外はデバッグのためにコンソールに出力される方がうれしいからです。
言い換えると、JavaScript開発者は例外に関して、アプリケーションのエラーハンドリングを目的としてErrorのサブクラスを作って投げることはほとんどありません。Errorのプロトタイプチェーンを気にすることはほとんどありません。
もし標準のエラーをモニタリングする仕組みが期待に合わなければ、onerror
を安全に置き換えて使用できます。
//すべてのエラーを無視する
m.deferred.onerror = function() {}
//エラーを単にログに出力する
m.deferred.onerror = function(e) {console.error(e)}
シグニチャ
Deferred deferred() {void onerror(Error e)}
where:
Deferred :: Object { Promise promise, void resolve(any value), void reject(any value) }
Promise :: GetterSetter { Promise then(any successCallback(any value), any errorCallback(any value)), Promise catch(any errorCallback(any value)) }
GetterSetter :: any getterSetter([any value])
GetterSetter { Promise then([any successCallback(any value) [, any errorCallback(any value)]]) } promise
promiseは、2つの計算のコールバックを持つ
then
メソッドを持っています。then
メソッドは親のpromiseの計算結果を受け取る、別のpromiseを作って返します。promiseはgetter-setter (
m.prop
参照)。resolve
とreject
のどちらを呼んでも、promiseは親の計算結果の値 (もしくはpromiseが親を持っていなければresolve
/reject
)を保持します。Promiseの
catch
メソッド呼び出しは、then(null, errorCallback)
と等価です。Promise then([any successCallback(any value) [, any errorCallback(any value)]])
このメソッドは、
resolve
、reject
メソッドに渡される値を受け取り、結果のpromiseに格納される値を処理して返す2つのコールバックを受け取ります。any successCallback(any value) (optional)
ルートの
deferred
のresolve
が呼ばれるとsuccessCallback
が呼ばれます。この設定がfalsyな値だったときのデフォルトの動作は、
function(value) {return value}
という関数と等価です。もしこの関数がundefinedを返してきたら、値がどんなでものであろうと、
value
引数がthenableキューの次のキューに渡されます。any errorCallback(any value) (optional)
ルートの
deferred
のreject
が呼ばれるとerrorCallback
が呼ばれます。この設定がfalsyな値だったときのデフォルトの動作は、
function(value) {return value}
という関数と等価です。もしこの関数がundefinedを返してきたら、値がどんなでものであろうと、
value
引数がthenableキューの次のキューに渡されます。returns Promise promise
void resolve(any value)
このメソッドは、deferredオブジェクトの子のpromiseの
successCallback
に値を渡します。void reject(any value)
このメソッドは、deferredオブジェクトの子のpromiseの
errorCallback
に値を渡します。-
m.deferred.onerror
void onerror(Error e)
このメソッドはpromiseコールバックの中で例外が発生するたびに呼ばれます。デフォルトでは、このイベントハンドラはこのエラーがErrorのサブクラスの例外で、Errorそのもののインスタンスでなければコンソールにその例外を出力します。そうでない場合は、Promises/A+の仕様に従います。