angular.jsをやってみる (20) - $q -

2014-04-10T00:00:00+00:00 angular.js JavaScript

公式リファレンス: http://docs.angularjs.org/api/ng/service/$q

以下の例だと非常に微妙なんだけれども、例えば

var Sample = (function() {

  var values = ["hoge", "fuga", "foobar"];

  function Sample() {
  }

  Sample.prototype.getName = function(cb) {
    setTimeout(function() {
      var index = Math.floor(Math.random() * values.length);
      cb(values[index]);
    }, 1000);
  }

  return Sample;

})();

なんていうのがあった場合に

angular.module("app", [])
  .factory("SampleFactory", function() {
    return {
      getName: function() {
        var name;

        var sample = new Sample();
        sample.getName(function(value) {
          name = value;
        });

        return name;
      }
   };
  })
  .controller("SampleController", function($scope, SampleFactory) {
    $scope.name = SampleFactory.getName();
  });

だなんてやってもSampleFactory.getNameで返される値はundefinedにしかならない。そりゃその値が返される際にまだコールバックが発生していないのでname変数は初期化すらされずundefinedになるっていうオチ

んまぁこういうパターンだと別にfactoryしなくても良いケースなので分かりづらいけど、要は非同期処理等が行われるにあたって、それが完了した際に通知するような仕組みを利用しなければ正常に値が取得できないような場合には$qなDeferredオブジェクトを利用する事で可能っぽい。っていう事で$qを使って上記コードを書き換えると

angular.module("app", [])
  .factory("SampleFactory", function($q) {
    return {
      getName: function() {
        var deferred = $q.defer();

        var sample = new Sample();
        sample.getName(function(value) {
          deferred.resolve(value);
        });

        return deferred.promise;
      }
   };
  })
  .controller("SampleController", function($scope, SampleFactory) {
    SampleFactory.getName().then(function(value) {
      $scope.name = value;
    });
  });

っていう感じかと。まぁ大体な使い方自体はjQuery.Deferredと同じ感じかと

で結局はserviceやfactory(controllerだとコールバックで$scope.$applyすりゃいいので)等において、結果が非同期によって処理されるようなケースの場合だと値を$q.promiseなDeferredオブジェクトを返しておいてthen等によって値を受け取るような仕組みを採用する事でカバー出来る感じ

余談

Chrome Extensionとかだとchrome.runtime.getBackgroundPageを使うもあると思うが、それ自体は非同期で処理されるのでservice等でChrome Extension Background Pageなスコープなオブジェクトを使う場合に

angular.module("twitterApp", ["ngRoute", "ngSanitize"])
  .service("twitter", function() {
    var twitter;

    chrome.runtime.getBackgroundPage(function(bg) {
      twitter = bg.twitter;
    });

    return twitter;
  })

なんていうのをやってもコントローラーで処理される際にこの値が返ってくるとは限らない(Event Pagesによりバックグラウンドページのインスタンスがアイドル化する事がある為に結果が返ってくるまでに遅延する事がある)。よってこういうケースにおいても

angular.module("twitterApp", ["ngRoute", "ngSanitize"])
  .service("twitter", function($q) {
    var deferred = $q.defer();

    chrome.runtime.getBackgroundPage(function(bg) {
      deferred.resolve(bg.twitter);
    });

    return deferred.promise;
  })

というようにDeferredオブジェクトを利用して値を取得するようなパターンにするべきかと

angular.jsをやってみる (21) - $logProvider - angular.jsをやってみる (19) - 動的に追加したエレメントに対して$animate.enter -