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;
}
我正在开发一个视频系列应用程序,其设置如下:
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;
}