Angular Promise Called Twice 仅推迟给第一个调用者

Angular Promise Called Twice is Only Deferring to First Caller

我正在开发一个视频系列应用程序,其设置如下:

angular.module('videoSeries', ['ngAnimate', 'ui.router'])
  .config(config)
  .factory('episodes', episodesFactory)
  .controller('MainCtrl', MainCtrl)
  .controller('EpisodeCtrl', EpisodeCtrl);

工厂'episodes' 加载两个控制器都需要读取的JSONP 文件。为了确保控制器等到 JSONP 成功返回,我在工厂中设置了这样的承诺 ():

episodesFactory.$inject = ['$http', '$q'];
function episodesFactory($http, $q) {
  var pub = {},
      jsonUrl = 'http://i.cdn.turner.com/nba/nba/.element/media/2.0/teamsites/warriors/json/json-wgtv.js?callback=JSON_CALLBACK' + (new Date().getTime()),
      cachedResponse;

  pub.getData = function() {
    var deferred = $q.defer();

    if (cachedResponse) {
      deferred.resolve(cachedResponse);
    }

    else {
      $http.jsonp(jsonUrl);

      window.jsonCallbackWGTV = function(data) {
        cachedResponse = data;
        deferred.resolve(data);
      }
    }

    return deferred.promise;
  }

  return pub;
}

然后两个控制器都在 .then returns 之后执行,如下所示:

MainCtrl.$inject = ['$scope', 'episodes'];
function MainCtrl($scope, episodes) {
  episodes.getData().then(function(data) {
    $scope.episodes = data.episodes;
  });
}

EpisodeCtrl.$inject = ['$scope', '$stateParams', 'episodes'];
function EpisodeCtrl($scope, $stateParams, episodes) {
  episodes.getData().then(function(data) {
    $scope.episode = data.episodes[$stateParams.episodeIndex];
  });
}

如果一次只有一个控制器调用 .getData(),这会工作得很好,但如果两个控制器同时调用它(如果您直接加载到剧集状态,就会发生这种情况),看起来好像忘记了一个承诺。

Here is a plunker。请注意,为了查看问题,您单击一集然后直接重新加载到该路线,但我不确定如何在 plunkers 中执行此操作,因为 URL 不会更新。

你的问题根本上是由远程资源引起的总是return将其数据包裹在jsonCallbackWGTV() {...}中,导致你的两个同时调用争夺window.jsonCallbackWGTV()谁笑到最后,谁就笑得最大声

JSONP 资源确实应该允许客户端指定回调的名称,但这可能是您无法控制的。

应该通过缓存延迟而不是传递给它的数据来解决这个问题。因此,最初的 .getData() 调用和所有后续调用都可以 return 从相同的延迟派生的承诺。

episodesFactory.$inject = ['$http', '$q'];
function episodesFactory($http, $q) {
    var jsonUrl = 'http://i.cdn.turner.com/nba/nba/.element/media/2.0/teamsites/warriors/json/json-wgtv.js',
    deferred;
    return {
        getData: function() {
            if (!deferred) {
                deferred = $q.defer();
                $http.jsonp(jsonUrl);
                window.jsonCallbackWGTV = function(data) {
                    deferred.resolve(data);
                    delete window.jsonCallbackWGTV;
                }
            }
            return deferred.promise;
        }
    };
}   

更典型的是,您会缓存 deferred 的 promise 而不是 deferred 本身,但是 jsonCallbackWGTV 问题使得有必要保留对 deferred 的引用,并且缓存的 deferred 也可以用于两者目的。

您可以通过在根状态下使用解析来回避这个问题。所有子状态都将继承已解析的数据。

.state('root', {
    url: '',
    abstract: true,
    views: {
      'episodesList': {
        templateUrl: '/episodelist.html',
        controller: 'MainCtrl'
      }
    },
    resolve: {
        'cachedEpisodes': ['episodes', function(episodes){
            return episodes.getData();
        }]
    }
  }

在这种情况下,当任何子状态激活此根状态时,解析应该只发生一次。

编辑: 您不需要在配置中包含剧集工厂。状态解析是对象映射。每个键都是一个名称,该名称将在该状态下的任何控制器或子控制器的注入中可用。

在状态变为活动状态之前,解析函数不会被执行,此时它将可以访问所有运行时(不是配置)注入。

我将函数包装在数组中可能看起来有点奇怪。数组的每个值都是可注入的名称,数组的最终值必须是它们被注入的函数。这在 angular 中启用,以帮助防止优化器出现破坏参数名称(否则将用于确定可注入)的问题。

哦,在路由更改完成之前,return 承诺的任何解析函数都将由 angular 完全解析,并且它们的解析值是注入控制器的值。

MainCtrl.$inject = ['$scope', 'cachedEpisodes'];
function MainCtrl($scope, data) {
  $scope.episodes = data.episodes;
}

EpisodeCtrl.$inject = ['$scope', '$stateParams', 'cachedEpisodes'];
function EpisodeCtrl($scope, $stateParams, data) {
  var i = 0, len = data.episodes.length, episode;
  for (i = 0; i < len; i++) {
    if (data.episodes[i].season === $stateParams.season && data.episodes[i].episode === $stateParams.episode) {
      episode = data.episodes[i];
      break;
    }
  }
  $scope.episode = episode;
}