被称为两次的角度承诺只是推迟到第一个来电者

时间:2015-03-11 00:07:42

标签: angularjs jsonp deferred angular-promise angular-services

我正在开发一个像这样设置的视频系列应用:

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

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

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之后执行,如下所示:

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。请注意,为了查看问题,请单击一个剧集,然后直接重新加载到该路径,但我不确定如何在浏览器中执行此操作,因为URL未更新。

2 个答案:

答案 0 :(得分:1)

您的问题基本上是由远程资源始终返回其jsonCallbackWGTV() {...}中包含的数据引起的,导致您的两个同时呼叫争夺window.jsonCallbackWGTV()最后笑的人,笑得最响亮

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

应该通过缓存延迟而不是传递给它的数据来克服问题。因此,原始的.getData()调用和所有后续调用都可以返回从相同的延迟派生的承诺。

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;
        }
    };
}   

更典型的是,您将缓存延迟的承诺而不是延迟本身,但jsonCallbackWGTV问题使得必须保留对延迟的引用,并且缓存的延迟也可以用于这两个目的。

答案 1 :(得分:1)

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

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

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

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

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

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

哦,在路由更改完成之前,任何返回promises的解析函数都将通过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;
}