mikutterには実行予約の機能がいろいろある。
RubyのThreadもある意味そうだけど、Delayerなんかもそう。これはJavaScriptでいうところのSetTimeout(callback, 0)みたいなもので、用途もウィジェットの更新とかなので被ってる。
で、JavaScriptではJSDeferredが便利だ
http.get('some_twitter_api').next(function(){
// success
}).error(function(){
// fail
})
いやーすばらしい。これはパクらない手はない
deferredは、連続した処理をぼちぼち順番にやっていって、エラーの捕捉もしやすい。で、http通信のメソッドとかがDeferredオブジェクトを返せば同じようにつかえて便利だなぁ。
ちょっと実験してみましょう。
現在のmikutterでは
例えばTwitterAPIを叩くのはPost#call_apiというメソッドを使う。こいつはAPI名とブロックを引数に取って、新しくスレッドを作ってリクエストを発行して、そのスレッドを返す。
あとでリクエストが完了したらコールバックを呼ぶ。という、JavaScriptのXMLHttpRequestだっけ、あれみたいな挙動をしている。スレッドを返す理由は、途中でThread#killを使ってキャンセルできるようにという深遠な理由がある。
Deferredを使うとどうなる
まず、今回の実験では、Deferredableをincludeした全てのクラスがdeferredみたいに振舞うことにした。これで、以下のオブジェクトが等しくDeferredのように振る舞える。
- Delayer
- SerialThread
- Thread
Deferredのブロックが返す値は、次のnext{}ブロックの引数になるけれど、それがDeferredのインスタンスだったら、それの実行を待ってから次のブロックが呼ばれる。つまりDeferredをネストできる。
ということは、Deferredのブロックの戻り値にThreadとかを設定できてしまう
deferred{
post.call_api(:api)
}.next{ |value|
// success
}.trap{ |exception|
// fail
}
Proc#call_apiの戻り値はThreadなので、本当は以下のように書ける
post.call_api(:api).next{ |value|
// success
}.trap{ |exception|
// fail
}
おなじこと。
こうする利点は、whenとかloopとかがつかえることかな
Deferred.when(deferred_1, deferred_2, ..., deferred_n).next{
// do something
}
こういう感じで、複数のDeferredableの終了を待てる。今までだったらDelayerとThread両方が終わったら…なんてコードはよう書かなかったんだけど、Deferredableならできますよどうですか!
Enumerableにeach_deferとか作っておけば、イベントで大量のつぶやきを受け取ったとき、時々休みながら実行ということができる。つまり、ループが長引いたら一旦処理を中断するというあれ。なかなかいいんじゃないかな
とまあ適当なことを書き散らしたけれど、まあ利点は本家とたいして変わらない。DeferredableをincludeすればどんなオブジェクトでもDeferredになれるので、いろんなクラスのエラー処理をとてもスマートに書ける可能性がありますね。
まだこれはコミットせずに様子を見てる。プラグインが書きやすくなるだろうか。