はじめてのPromise

CYBIRDエンジニア Advent Calendar 2014 18日目の@doidoidonです。

ここ数ヶ月は久々にブラウザアプリの開発にどっぷりつかり、世の中の動きについていけてません。。。みなさまはいかがおすごしでしょうか？世間はすっかりクリスマスイルミネーション一色で、我が家も負けじとクリスマスツリーを飾りました！！（がそれぐらいです。。。あと一応選挙には行きました。）
昨日は内にアツイ想いを秘めた@RYad0902さんが、[スマートフォンアプリの開発工数について](http://qiita.com/RYad0902/items/34cde69648dd04b43d6f)いろいろ"あるある"を教えてくれましたね！最近Androidの標準ブラウザの挙動に苦しめられまくりの自分としては、とても身につまされる内容で、もう少し早く教えてくれれば・・・いやいや頑張りましょうー

## 本日の内容

最近は@dekokunさんや@megadreams14さんたちとBackbone.Marionetteを使ったブラウザアプリ開発をしてますが、恥ずかしながらそのプロジェクトで始めてPromiseなるものを使ってみましたので、そのご紹介です

## Promiseとは

細かい話は[こちら](http://www.html5rocks.com/ja/tutorials/es6/promises/#toc-async)や[こちら](http://azu.github.io/promises-book/)などに詳しく書かれてますので、ご参照ください。端的に言うと、Ajaxなどブラウザアプリにおいて必須の非同期処理をコントロールするために便利な仕組み、です

## Promiseしないで書いたコード

例えば、以下のようなBackboneのModelを使ってサーバのデータを取得する場合、呼び元側は通信成功・失敗に対応したCallbackメソッドを用意し後続処理を行います

~~~JS
var Hoge = Backbone.Model.extend({
    urlRoot: "/hoge"
});
var hoge = new Hoge();
hoge.fetch({
    success: function(model) {
        // 取得成功の処理
    },
    error: function(model) {
        // 取得失敗の処理
    }
});
~~~

## Promiseを使った場合

以下のようにPromiseを使った場合、サーバのデータを取得した後はPromiseオブジェクトで用意したメソッド（この場合then）を呼んで後続処理を行います

~~~JS
var hogePromise = function() {
    return new Promise(function (resolve, reject) {
        var Hoge = Backbone.Model.extend({
            urlRoot: "/hoge",
        });
        var hoge = new Hoge();
        hoge.fetch({
            success: function(model) {
                resolve(model);
            },
            error: function(error) {
                reject(error);
            }
        });
    });
};
var hoge = hogePromise();
hoge.then(function(result){
    // 取得成功の処理
}).catch(function(error){
    // 取得失敗時の処理
});
~~~

## Promiseのメリット

上記の例だと元々がシンプルなのでそれほどメリットを感じにくいかと思いますが、通常のアプリ開発において数十本のサーバAPI通信やリソースデータ取得などはよくあり、アプリケーションの局面毎に同時に必要とするサーバAPI処理とリソースデータ取得処理の組み合わせが複雑化していくにつれて、Promiseは非常に威力を発揮します

例えば3種類のサーバAPI通信を使って何かしたい局面において、Promiseを使わない場合、以下のようなやな感じのネストしたコードになります

~~~JS
var hoge1 = new HogeModel1();
var hoge2 = new HogeModel2();
var hoge3 = new HogeModel3();
hoge1.fetch({
    success: function(model) {
        hoge2.fetch({
            success: function(model) {
                hoge3.fetch({
                    success: function(model) {
                        // 取得成功の処理
                    },
                    error: function(model) {
                        // 取得失敗の処理
                    }
                });
            },
            error: function() {
                // 取得失敗の処理
            }
        });
    },
    error: function(model) {
        // 取得失敗の処理
    }
});
~~~

これをPromiseを使うと以下のようになります

~~~JS
var hoge1 = hogePromise1();
var hoge2 = hogePromise2();
var hoge3 = hogePromise3();
Promise.all([hoge1, hoge2, hoge3]).then(function(results){
    var hoge1Result = results[0],
    hoge2Result = results[1],
    hoge3Result = results[2]
    // 取得成功の処理
}).catch(function(error){
    // 取得失敗時の処理
});
~~~

全体がシンプルになるだけでなく3つの処理を並列に行うことで全体のスループットを向上させることができます。今回はPromise.allで並列化しましたが、普通にthenメソッドでつないだ逐次処理もできますし、Promise.raceを使うと3つのうち最初の1つが終了した時点で後続処理を行う、といったこともできます。便利ですねー

## Promiseの現状

スマートフォンのブラウザで実装するとなった時に、こちらで見る[ブラウザ対応状況](http://caniuse.com/#feat=promises)はちょっと寒い感じですが、現在策定中のES6 Promisesの前身であるPromises/A+互換ベースのライブラリがいくつかあり、それを利用して実装することでサポート外のブラウザでも動いたりします。自分たちのプロジェクトでは、[bluebird](https://github.com/petkaantonov/bluebird)と呼ばれるPromiseライブラリを使って実装してますが、非常に使いやすいだけでなく、今のところAndroid4系の標準ブラウザなどのくせのある環境でも問題なく動作しております（ただしAndroid2系は駄目でした。。。）

## 最後に

ここ10年近くJava、PHPなどのサーバサイド言語を使って色んなサイト構築をしてきましたが、久々にイベント駆動の本格的なクライアントアプリケーションの開発に携われてとても新鮮です。ハイ（思えば若い頃はC言語とかVBとか亜流だとOracleのD/2000とか使ってC/Sなシステムを色々作ったなー）
あまりに久々すぎてGUIプログラミング特有の手間ひまを考慮せずに安易な見積であっさりスケジュール遅延などもしてしまったが。。。まあ次につながるということで。。。明日は、次のCYBIRDの売上を担う（かもしれない）新型ゲームを絶賛開発中のいぶし銀@khironoriさんで、テーマはどうやら開発プロセスやルール化に関する内容みたいですよ。お楽しみにー