ちょっと話題の記事

[AngularJS] $q サービスで覚える Promise

2014.07.18

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

angularjs125title

車輪開発大好きおたいがです。こんにちは。(挨拶)

今回は JavaScript で非同期処理を実施するときに用いられる Promise ( プロミス ) についてまとめてみました。Promise とは「非同期処理を抽象化したオブジェクトと、そのオブジェクトを操作する仕組み」のことを指します。

引用 : https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise より

Promise インターフェースは作成時点では分からなくてもよい値へのプロキシです。プロミスを用いることで、非同期アクションの成功や失敗に対するハンドラを関連付けることができます。 これにより、非同期メソッドは、最終的な値を返すのではなく、未来のある時点で値を持つプロミスを返すことで、同期メソッドと同じように値を返すことができるようになります。

また、ECMAScript 6 Promises の仕様を中心とした下記ドキュメントにて丁寧に解説されているので、興味のある方は読んでみるのも良いかもしれません。

JavaScript Promise の本
http://azu.github.io/promises-book/

Deferred オブジェクト

ゼロベースで Promise オブジェクトを生成するときには、まず最初に AngularJS の $q サービスの $q.defer() メソッドを実行して Deferred オブジェクトを生成することから始まります。

Deferred オブジェクトの promise プロパティには Promise オブジェクトが用意されています。

var deferred = $q.defer();
var promise  = deferred.promise;

Promise オブジェクトのメソッド

Promise オブジェクトが提供する主なメソッドは以下のとおりです。

メソッド 解説
promise.then(
  successCallback,
  errorCallback,
  notifyCallback
)
promise 処理の
成功 (successCallback),
失敗 (errorCallback),
通知 (notifyCallback)
という、各状況に応じて実行させたいコールバック関数を登録
promise.catch(errorCallback) promise.then(null, errorCallback) と同義
promise.finally(finallyCallback) promise 処理の最後に実行させたいコールバック関数を登録
IE8 と Android 2.x 系をサポートする場合
promise['finally'](callback)
と書く必要がある

promise.then() メソッドで登録したコールバック関数の実行タイミング

promise.then() メソッドで登録したコールバック関数は、下記 Deferred オブジェクトのメソッドを実行したときに実行されます。( 自前で生成した Deferred オブジェクト経由で生成した Promise オブジェクトの場合 )

メソッド 解説
deferred.resolve() promise 処理の成功を示す
successCallback が実行される
deferred.reject() promise 処理の失敗を示す
errorCallback が実行される
deferred.notify() promise 処理中における通知を示す
notifyCallback が実行される

次の例は、処理開始の 1 秒後 ( 1000 ミリ秒後 ) に、promise.then() メソッドで登録したコールバック関数が呼び出されるサンプルコードです。

var getPromise = function() {
    var deferred = $q.defer();
    setTimeout(
        function() {
            var notifyObj;
            // 処理の通知を示す promise.then() の notifyCallback をコール ( 引数オブジェクトを渡せる )
            deferred.notify(notifyObj);
            
            if ( /* 任意の評価 */ ) {
                var resolveObj;
                // 処理の成功を示す promise.then() の successCallback をコール ( 引数オブジェクトを渡せる )
                deferred.resolve(resolveObj);
            } else {
                var rejectObj;
                // 処理の失敗を示す promise.then() の errorCallback をコール ( 引数オブジェクトを渡せる )
                deferred.reject(rejectObj);
            }
        }
    }, 1000);
    return deferred.promise;
};

var successCallback = function(resolveObj) { /* TODO: */ };
var errorCallback   = function(rejectObj) { /* TODO: */ };
var notifyCallback  = function(notifyObj) { /* TODO: */ };
var finallyCallback = function() { /* TODO: */ };

var promise = getPromise();
promise
.then(successCallback, errorCallback, notifyCallback)
.finally(finallyCallback);

Promise オブジェクトによるメソッドチェーン

promise.then() メソッドは、新しい Promise オブジェクトを戻り値として返すので、次のような記述が実現できます。

var promise = $q.defer().promise;
…
promise
.then( function() { /* TODO: */ } )
.then( function() { /* TODO: */ } )
.then( function() { /* TODO: */ } )
.then( function() { /* TODO: */ } );

$q.all() メソッドによる複数 Promise オブジェクトの監視

たとえば、同時に複数の非同期処理を走らせて、すべての処理が完了した後に任意の処理を実施させたい …といったときに $q.all() メソッドは役に立ちます。

var promiseA = getPromiseA(); //任意の非同期処理 A
var promiseB = getPromiseB(); //任意の非同期処理 B
var promiseC = getPromiseC(); //任意の非同期処理 C
var promiseD = getPromiseD(); //任意の非同期処理 D
…
var promiseAll = $q.all([
                     promiseA,
                     promiseB,
                     promiseC,
                     promiseD
                 ]);
promiseAll.then(
    function() {
        //TODO: すべての非同期処理が終わった後の処理
    }
);

$q に依存する AngularJS のサービス

下記サービスでは内部的に $q サービスが使用されており、それぞれ戻り値として Promise オブジェクトを取得することができます。

サービス 機能
$interval window.setInterval のラッパー
任意の時間間隔で繰り返し処理を実施
$http XMLHttpRequest オブジェクト、または、JSONP を通じて HTTP サーバと通信するサービス
$resource $http の高級機能版 ( 要・ngResource モジュール )
$timeout window.setTimeout のラッパー
任意の遅延時間後に指定関数を実行

$timeout を使用した非同期処理サンプル

$timeout サービスと $q.all() メソッドを利用すると以下のようなことが実現できます。

まとめ

AngularJS の $q サービスの理解を深めることによって、$q に依存する他のサービス ($interval, $http など ) の理解はより深まります。もし、理解度に自信が無いようであれば、上記のような簡単なサンプルをひとつでも作ってみると良いかもしれません。