いまさら JSDeferred と $.Deferred を少しさわったメモ

コールバック地獄的なそういうあれを垣間見てしまったので、たしかにもうちょっといいかんじに解決したいってのがよくわかった。そこで書き方を変えることでいい感じに . でチェインしたり例外処理をしやすくするために Deferred がある。ので、簡単なメモとかじったサンプルを張る。

あとは以下の記事に触発されたのもある
JavaScriptとコールバック地獄 - Yahoo! JAPAN Tech Blog
JavaScriptと非同期のエラー処理 - Yahoo! JAPAN Tech Blog
爆速でわかるjQuery.Deferred超入門 - Yahoo! JAPAN Tech Blog

同期と非同期

JS にかぎらず、同期と非同期という考え方がある。処理を逐次行なっていって順番に処理していくのが同期的だとすると、いったんスタックやキューにためておいて「あとでやるからちょっとまって」というように遅延させておくのが非同期処理。Python/Django だと普通に書けば同期的になるけど、celery と RabbitMQ あたりをつかったりさせて非同期処理でバッチさせるというようなことがある(まああとはOS側である程度スレッドつかってくれたりもする)。

で、JavaScript において。発端としてブラウザで動くものとしてあるのでシングルスレッドであると。だから重たい処理をさせるとブラウザが固まる。そうなると困るよね、ってことで、setTimeout とか XMLHttpRequest するところあたりでは同期ではなく非同期処理となってる(という理解)。だってブラウザがいろいろ通信してる間に固まったら困るよねっていう。

たとえば同期的な sleep を実装するとこうなる。

function sync_sleep(ms) {
  var start = new Date().getTime();
  var end = start + ms;

  while (true){
    var tmp = new Date().getTime();
    if (end < tmp){
      break;
    }
  }
}

でも長いと固まるしブラウザが中断してくるかもしれない。じゃあ非同期にする。

function async_sleep(ms) {
  setTimeout(function () {
    console.log(1);
    setTimeout(function () {
      console.log(2);
      setTimeout(function () {
        console.log(3);
        setTimeout(function () {
          console.log(4);
        }, ms);
      }, ms);
    }, ms);
  }, ms);
}

で、そうすると素直に書くとコールバック関数がネストして眠くなってくる。実際は xhr や $.ajax のようなことをネストしたくなったり、あとは Node で本格的に書こうとするとだいぶつらぽよくなってくる

Deferred の共通の考え方 - callback による非同期処理を簡潔にする

setTimeout(callback, 0) をいれておくことで擬似的にスタックに詰めておくようなことができる。ハック。ブラウザの UI スレッドを止めない。すごい。具体的な資料は以下。はてなの @cho45 さん。2009 年にはやってたことなんだよなぁ。
cho45/jsdeferred · GitHub
JSDeferredがやっとわかった - by edvakf in hatena
JSDeferredで,面倒な非同期処理とサヨナラ:特集|gihyo.jp … 技術評論社

おぼろげだけどソース読んだりもした。自分の理解としては

  • Deferred オブジェクトを生成して管理
  • Deferred オブジェクトを起点に処理を next や error でチェインしてかける
  • 内部的には next や error ごとに Deferred オブジェクトを生成して管理してやる -> チェインできる

というくらいの理解

JSDeferred をちょっとだけかいた

var dfd = require("/path/to/jsdeferred.js").Deferred;

dfd.next(function () {
  console.log(1);
}).wait(1).next(function () {
  console.log(2);
}).wait(1).next(function () {
  console.log(3);
}).wait(1).next(function () {
  throw "throw!";
  console.log(4);
}).error(function () {
  console.log('error');
}).next(function () {
  console.log(5);
}).next(function () {
  console.log(6);
});

いい感じですね。

JSDeferred と $.Deferred の対比

JSDeferred もあるけど、 jQuery(1.5から)も似たような機能がついた。だいたい似てるけどちょっとまとめる
uu59のメモ | jQuery.Deferredその1: JSDeferredとの基本的な違い
jQuery.Deferredって何 - Takazudo hamalog
jQueryのDeferredオブジェクトについて調べてみた - AOEの日記
ちゃんと検証してないけど、自分なりの理解としてはだいたいこんな感じ

  • JSDeferred の next, error に対して jQuery.Deferred の done, fail
  • JSDeferred の call, fail に対して jQuery.Deferred の resolve, reject
  • jQuery.Deferred ではユーザーに状態を変更されないように promise して Deferred オブジェクトを返す
  • JSDeferred の parallel に対して ちょっとちがうけど jQuery.Deferred では when がある
  • jQuery.Deferred だと then で dfd(resolve, reject) とかける

まああとは実際に使う機会があったらちゃんとやる。

まあ

そもそもにおいて setTimeout(callback, 0) という発想がすごい。あとは使う機会があったらちゃんとやる。この手の問題はだいぶ認知されてるみたいで、async系のライブラリは npm とか探すと出てくる。