mikutterの最新の情報は、mikutter blogに引っ越しました。

2011年7月11日月曜日

mikutterにDeferred入れたらどうなるのっと

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になれるので、いろんなクラスのエラー処理をとてもスマートに書ける可能性がありますね。
まだこれはコミットせずに様子を見てる。プラグインが書きやすくなるだろうか。