WebOS Goodies

WebOS の未来を模索する、ゲームプログラマあがりの Web 開発者のブログ。

WebOS Goodies へようこそ! WebOS はインターネットの未来形。あらゆる Web サイトが繋がり、共有し、協力して創り上げる、ひとつの巨大な情報システムです。そこでは、あらゆる情報がネットワーク上に蓄積され、我々はいつでも、どこからでも、多彩なデバイスを使ってそれらにアクセスできます。 WebOS Goodies は、さまざまな情報提供やツール開発を通して、そんな世界の実現に少しでも貢献するべく活動していきます。
Subscribe       

Closure Library の非同期処理を簡単にする「goog.result」を使ってみる

本日は Closure Library に新しく加わった「goog.result」というモジュールについてです。しばらく goog.labs 名前空間の下で開発が続けられていたのですが、先日 labs を卒業 (?) して、正式な機能となりました。

この goog.result は、いわゆる promise パターンを Closure Library 流に実装したものです。いわゆる jQuery.Deferred なわけですが、単なるコピーではなく、あくまで Closure Library のスタイルで設計されているのが面白いところです。おそらく今後は多くの非同期処理が goog.result ベースで書き換えられていくでしょうから、 Closure Library 使いの方は今のうちに把握しておくのがおすすめです。

基本的な使い方

goog.result の基礎となるインターフェースは goog.result.Result で定義されています。しかし、これは本当に純粋なインターフェース定義なので、実際にインスタンス化することはありません。実装まで含んだクラスとしては、goog.result.SimpleResult が用意されています。あまり意味のある例ではありませんが、最も基本的な使用例は以下のような感じです。

// SimpleResult インスタンスを作成する
var result = new goog.result.SimpleResult();

// Okボタンの処理
goog.result.waitOnSuccess(result, function(text, result) {
  alert(text);
});

// Cancalボタンの処理
goog.result.waitOnError(result, function(result) {
  alert('cancel');
});

// confirmを表示し、その結果をresultに設定する
confirm('Push ok or cancel.') ? result.setValue('Ok') : result.cancel();

goog.result.SimpleResult のインスタンスを作成し、waitOn(Success|Error) で成功時、およびエラー時に実行する処理を登録しておきます。その後、 SimpleResult インスタンスの setValue() を呼べば成功時の処理が、 cancel() なら失敗時の処理が実行される、という仕組みです。メソッドチェインを使わないのが奇異に感じるかもしれませんが、可読性や Closure Compiler による最適化を考慮しての仕様でしょう。

waitOnSuccess() で登録したコールバックの引数は、第一引数が setValue() の第一引数と同じもの、第二引数が SimpleResult インスタンスです。 waitOnError() のほうの引数は SimpleResult インスタンスのみとなっています。ちなみに、 SimpleResult の setError() / getError() メソッドを使えば、エラー時にもなんらかの値を渡すことはできます。

もう少し実用的な例

上の例ではなにが便利だかわからないので、もう少し実用的な例を示します。 goog.ui.Prompt を表示して、入力された値を alert で表示します。

// ダイアログを表示し、入力値を取得するための SimpleResult インスタンスを返す
function getPromptResult(caption) {
  var result = new goog.result.SimpleResult();
  var prompt = new goog.ui.Prompt('goog.result example', caption, function(text) {
    if(goog.isDefAndNotNull(text)) {
      result.setValue(text);
    } else {
      result.cancel();
    }
    prompt.dispose();
    prompt = null;
  });
  prompt.setVisible(true);
  return result;
}

// 上記の関数でダイアログを表示、結果を処理する
var result = getPromptResult('文字列を入力してください');
goog.result.waitOnSuccess(result, function(text) {
  alert(text);
});

最初に定義している getPromptResult() はコールバック関数を受け取ったりせず、ダイアログの選択結果を(将来的に)格納する Result オブジェクトを返します。呼び出し側ではその結果が成功かどうかを判定し、それに応じた処理を行います。 getPromptResult() 内の処理は少々泥臭いですが、呼び出し側のコードは処理結果の取得 → 結果の処理という通常のフローで処理が記述できています。

非同期通信の処理

promise パターンが真価を発揮するのは、やはりサーバーとの通信ですよね。残念ながらまだ goog.labs の名前空間に入ったままなのですが、 goog.result.Result オブジェクトを返す XHR 関数が用意されていて、 promise パターンでの通信処理が簡単に書けるようになっています。

var result = goog.labs.net.xhr.get('path/to/some.txt');
goog.result.waitOnSuccess(result, function(text) {
  alert(text);
});

異様に面倒だった従来の goog.net.XhrIo と比べて画期的なほどシンプルですね。上記は GET リクエストの例ですが、結果を JSON で受け取る getJson() や POST リクエストを送る post(), postJson() なども用意されています。また、それらのメソッドには省略可能な opt_option 引数があり、リクエストヘッダなどのカスタマイズも可能です。詳細はソースを見てください ^^;

チェイン

複数の処理をシリアルに実行することも可能です。例えば、最初のリクエストで取得した URL に対して二回目のリクエストを投げる処理は以下のようになります。

// リクエストを投げる
var chainedResult = goog.result.chain(
  goog.labs.net.xhr.get('path/of/first_request.txt'),
  function(firstResult) {
    return goog.labs.net.xhr.get(firstResult.getValue());
  });

// 結果を取得
goog.result.waitOnSuccess(chainedResult, function(text) {
  alert(txt);
});

goog.result.chain() の第一引数は最初に処理する Result オブジェクト、第二引数は最初の結果が成功したら実行されるコールバック関数で、戻り値として 2 つめの Result オブジェクトを返します。 goog.result.chain() の戻り値 (chainedResult) もやはり Result オブジェクトで、チェインした 2 つの処理が共に成功すれば 2 つめの結果が反映され、ひとつでも失敗したら失敗した処理の結果が反映されます。

3 段、 4 段と処理を連ねたいときは、 chainedResult に対してさらに goog.result.chain() を呼び出すことで実現できます。

コンバイン

複数の処理を並列に実行することも可能です。複数の URL に対して同時にリクエストを投げて、すべての結果が揃うのを待つ処理は以下になります。

// リクエストを投げる
var combinedResult = goog.result.combineOnSuccess(
  goog.labs.net.xhr.get('path/to/data1.txt'),
  goog.labs.net.xhr.get('path/to/data2.txt')
  // さらに何個でも処理を並べられる
  );

// 結果を取得
goog.result.waitOnSuccess(combinedResult, function(results) {
  var texts = goog.array.map(results, function(result) {
    return result.getValue();
  });
  alert(texts.join("\n"));
});

goog.result.combineOnSuccess() の戻り値 (combinedResult) もやはり Result オブジェクトです。引数として指定したすべての処理の結果が成功すれば chainedResult も成功し、ひとつでも失敗したら chainedResult も失敗します。いずれの場合も chaindResult の値は combineOnSuccess() の全引数の配列なので、そこから個々の処理の結果が取得できます。

トランスフォーム

goog.result のユニークな機能がトランスフォームです。任意の関数を指定して、非同期処理の結果を加工することができます。 XHR で取得した JSON データの中から entry フィールドの値を抜き出し、それを処理結果とする例を以下に示します。

// リクエストを投げる
var result = goog.labs.net.xhr.getJson('path/to/some.json');

// 結果を加工する
goog.result.transform(result, function(json) {
  return json['entry'];
});

// 結果を処理する
goog.result.waitOnSuccess(result, function(entry) {
  // ...
});

結果の生成側 (goog.labs.xhr) にも処理側(waitOnSuccess() のコールバック)にも手を入れずに両者を取り持つことができるのが面白いですね。 chain() を使っても同じことはできますが、こちらのほうがよりシンプルです。 combineOnSuccess() の処理結果をひとつにまとめたり、結果のフォーマットが複数ある場合にその差異を吸収したりと、いろいろな用途が考えられますね。

goog.async.Deferred へのブリッジ

Closure Library には、実は以前から goog.async.Deferred という Mochikit からポートした Deferred ライブラリが提供されていました。 goog.result はそれを置き換えるものではなく、一般的なユースケースをよりシンプルに記述できるものとして設計されています。したがって、より複雑なケースでは、 goog.async.Deferred を使う必要が出てくるかもしれません。

そんなときのために、 Result オブジェクトを goog.async.Deferred オブジェクトに変換するアダプタが提供されています。

var result = goog.labs.net.xhr.get('path/to/some.txt')

// Result オブジェクトを goog.async.Deferred に変換
var deferred = new goog.result.DeferredAdapter(result);
goog.async.Deferred.when(deferred, function(value) {
  alert(value);
});

goog.result と goog.async.Deferred の両方を知っていれば、非同期処理は怖いものなしですね :)

という感じで、 Closure Library の新しい非同期処理モジュール goog.result の使い方をご紹介しました。 Closure Kitchen の Closure Library も goog.result をサポートしたバージョンに更新して、上記のコード例とほぼ同様のサンプルも追加しておきましたので(Asynchronous というやつです)、ぜひいじってみてください。

関連記事

この記事にコメントする

Recommendations
Books
「Closure Library」の入門書です。
詳しくはこちらの記事をどうぞ!
Categories
Recent Articles