Top / AngularJS / TIPS集 / 非同期処理のいろいろ

Angularでプログラム書いていて、サーバからの結果を画面表示するためにコールバックでゴニョゴニョしたり、時間がかかったらタイムアウトしたり、一度だけ取っときゃいいデータはサービスでストアしといたり、一度に複数データを取って全部戻ってきたら処理をしたかったり、そういう非同期関連の処理を調べてて、いろいろメモっとかなきゃとおもいwikiっとく事にしました。。

ってもまだ書き途中ですけど。。

.controller('Menu7Ctrl', function (sampleRestService1, $scope) {
    var p4 = sampleRestService1.getWeather();
    console.log(p4);
    p4.then(function (sharedService1) {
            console.log(sharedService1);
            $scope.result1 = sharedService1.get(0);
            $scope.result2 = sharedService1.get(1);
        }, function (data) {
            console.log(data);
            console.log('失敗!');
        }
    );
})

Controllerで非同期通信したい場合は、promiseを返却するサービスを作成して、結果をコールバックでセットするのが一般的*1。thenに渡すメソッドは、成功した場合と失敗した場合それぞれの処理ですね。。

このサービス(sampleRestService1) は、サーバにRESTでアクセスしてなんか値を返すとかそんなモノをイメージしてください。

.factory('sampleRestService1', function ($resource, sharedService1, $q) {
        return {
            getWeather: function () {
                var d = $q.defer();
                if (!(sharedService1.isEmpty(0) || sharedService1.isEmpty(1))) {
                    console.log("キャッシュデータを使う");
                    d.resolve(sharedService1);
                } else {
                    var r1 = $resource('/api/weather1.json');
                    var r2 = $resource('/api/weather2.json',
                        {},
                        {'get': {method: 'GET', timeout: 3500}}
                        // 3.5sでタイムアウトとした
                    );

                    $q.all([r1.get().$promise, r2.get().$promise]).then(
                        function (result) {
                            console.log(result[0]);
                            console.log(result[1]);
                            sharedService1.add(result[0]);
                            sharedService1.add(result[1]);

                            d.resolve(sharedService1);
                            console.log("ホンモノデータを使う。そのあとキャッシュを生成");
                        },
                        function (result) {
                            console.log("$q.allの一つが失敗した");
                            d.reject(result);// $q.all()の失敗を検知してメインのdeferも失敗とする
                        }
                    );
                }
                return d.promise;
            }
        }
    }
)
.factory('sharedService1', function () {
    var _data = [];

    // Public API here
    return {
        isEmpty: function (index) {
            return _data[index] == null;
        },
        get: function (index) {
            return _data[index];
        },
        add: function (val) {
            _data.push(val);
        }
    };
})

サービスはこんな感じにしてみました。ふたつのRESTを処理して、返ってきたデータを別のサービス(sharedService1)に格納してます。

ふたつの結果が返ってきてから sharedService1にセットするために 各$resourceのpromiseを使ってしまった(?)ので、

var d = $q.defer();
//長い処理。おわったら、、
d.resolve();
return d.promise;

って、新たなpromiseをつくって返してます。。

以下、いろいろなTIPSです

非同期処理を実行する、var d = $q.defer() / d.promise;

$resourceなどでも使用されている、promiseについてです。promiseの仕組みは、非同期処理を行うためにとりあえず呼びだし元にpromiseオブジェクトを返却し、実際に処理が完了したときに、さっき返したpromiseオブジェクトのメソッドを呼び出します。

上の処理でうところの、

var promise = sampleRestService1.getWeather();
promise.then(function (sharedService1) {
        console.log(sharedService1);
        $scope.result1 = sharedService1.get(0);
        $scope.result2 = sharedService1.get(1);
    }, function (data) {
        console.log(data);
        console.log('失敗!');
    }
);

ココですね。sampleRestService1.getWeather() はとりあえず promiseを返しておいて、実際に処理が完了すると、thenに渡しておいたメソッドが実行されます。一つ目のメソッドは成功時によばれるメソッド、二つ目のメソッドは失敗時によばれるメソッドです。

さて、ではsampleRestService1.getWeather()はどうやって非同期処理を実現しているかですが、中身を見てみると、整理すると以下のようになります。

var d = $q.defer();
いろいろ時間がかかる処理
// 完了できたらresolve
d.resolve(sharedService1);
// 処理が失敗してしまったら reject
d.reject(result);
return d.promise;

Angularが提供する$qサービスを用いて

var d = $q.defer();

という参照を取得し、処理が完了したら d.resolve、失敗してしまったらd.reject をコールするようにしておきます。最後にd.promiseでpromiseを返却します。

複数の非同期処理を実行する、$q.all

続いて複数の非同期処理を実行する場合です。サンプルではRESTでサーバに2回データを取得しに行って、両方とも戻ってきたら処理を進めるようにしています。

具体的には以下の箇所:

var r1 = $resource('/api/weather1.json');
var r2 = $resource('/api/weather2.json');
$q.all([r1.get().$promise, r2.get().$promise]).then(
    function (result) {
        console.log(result[0]);
        console.log(result[1]);
        sharedService1.add(result[0]);
        sharedService1.add(result[1]);
        d.resolve(sharedService1);
    },
    function (result) {
        console.log("$q.allの一つが失敗した");
        d.reject(result);// $q.all()の失敗を検知してメインのdeferも失敗とする
    }
);

ココでは

r1.get().$promise
r2.get().$promise

などと $resourceがもっているpromiseオブジェクト(まさにさっきやったヤツ)を複数 $q.all() に渡しています。 $q.all()の戻り値もpromiseオブジェクトになっていて、二つの$resourceが完了するとthenで渡したメソッドがコールバックされます。ちなみに二つ目の失敗時メソッドは、複数のpromiseどれか一つでも失敗したらコールされます。たとえばr2がタイムアウトするなど、そんなケースですね。。

promise便利ですね。。

実行結果をキャッシュする。

たとえばサーバから固定的なデータを一度だけ取得するなど、そんなことを考えてみます。このサンプルは ふたつある$resourceはそれぞれ一度だけ実行し、その結果をsharedService1 にとっておいて、2回目以降ではそれを返すようにしています。

具体的にはこんな感じ。

sampleRestService1 は sharedService1 をInjectionしていますが、そのsharedService1 は、

.factory('sharedService1', function () {
    var _data = [];
    return {
        isEmpty: function (index) {
            return _data[index] == null;
        },
        get: function (index) {
            return _data[index];
        },
        add: function (val) {
            _data.push(val);
        }
    };
})

とオブジェクトを追加できるようにしてあります。そして、sampleRestService1#getWeatherメソッドは、

getWeather: function () {
    var d = $q.defer();
    if (!(sharedService1.isEmpty(0) || sharedService1.isEmpty(1))) {
        console.log("キャッシュデータを使う");
        d.resolve(sharedService1);
    } else {
        var r1 = $resource('/api/weather1.json');
        ... 時間がかかる処理。一度だけやるようにしたい
        d.resolve(sharedService1);
    }
    return d.promise;
}

というように、sharedService1 に値が入っていたらそれを返す、なかったらsharedService1 をつくって、それを返す、というようにしてあります。

sampleRestService1や sharedService1 などAngularのサービスはSingletonなので、ほかのControllerから

var result0 =  sampleRestService1.sharedService1.get(0);

などとアクセスしても、キャッシュがあればそれを返すようにすることが出来ます。

いろいろな画面で共通に利用される情報や、プルダウンのデータとか、毎回サーバに問い合わせることナシに、でもログイン時になんでもとっておこうとすると時間かかるし、、みたいな要件で遅延ローディングとしてつかえそうです。


この記事は

選択肢 投票
おもしろかった 4  
そうでもない 0  

Top / AngularJS / TIPS集 / 非同期処理のいろいろ

現在のアクセス:3382


*1 $resourceのgetとかをControllerから呼べば?については後述。

トップ   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS