我有以下控制器代码:
.controller('Controller1', function ($scope, MyService) {
var promise = MyService.getData();
promise.then(function(success) {
console.log("success");
}, function(error) {
console.log("error");
}, function(update) {
console.log("got an update!");
}) ;
}
在我的services.js中:
.factory('MyService', function ($resource, API_END_POINT, localStorageService, $q) {
return {
getData: function() {
var resource = $resource(API_END_POINT + '/data', {
query: { method: 'GET', isArray: true }
});
var deferred = $q.defer();
var response = localStorageService.get("data");
console.log("from local storage: "+JSON.stringify(response));
deferred.notify(response);
resource.query(function (success) {
console.log("success querying RESTful resource")
localStorageService.add("data", success);
deferred.resolve(success);
}, function(error) {
console.log("error occurred");
deferred.reject(response);
});
return deferred.promise;
}
}
})
但由于某种原因,deferred.notify
调用似乎永远不会执行并在控制器中被接收。我这里有什么不对吗?我不知道如何让通知执行。
答案 0 :(得分:21)
我设法通过在$ timeout函数中包装notify来实现它:
$timeout(function() {
deferred.notify('In progress')}
, 0)
看起来你无法在返回promise对象之前调用notify,这有点意义。
答案 1 :(得分:2)
来源:http://www.bennadel.com/blog/2800-forcing-q-notify-to-execute-with-a-no-op-in-angularjs.htm
强制$ q .notify()执行
.notify()事件的美妙之处在于,我们的数据访问层可以使用它来提供即时可用但陈旧的"数据,同时仍然使用.resolve()事件,除了实时数据。这为调用上下文 - 您的控制器 - 提供了很好的洞察力和对缓存数据集的控制,以及它[控制器]是否甚至想要合并缓存数据。
但是,我们遇到了一些竞争条件。拥有缓存数据的数据访问服务需要在将promise返回给调用上下文之前调用.notify()。这意味着您的控制器在调用.notify()之后绑定到notify事件。从哲学的角度来看,这应该没问题 - Promises(以及几乎所有事件驱动的)都旨在异步调用绑定,以便创建访问的一致性。
然而,从实际的角度来看,它并不那么简单。虽然AngularJS遵循这一理念,但它也增加了一些优化来减少处理。在我们的具体情况下,AngularJS不会在延迟对象中安排回调处理,除非它发现至少有一个回调被绑定(否则它认为世界不是在监听)。因此,我们的控制器永远不会收到有关缓存数据的通知。
为了解决这个问题,我们可以让服务层在调用.notify()之前将无操作(无操作)函数绑定到notify事件。这样,当它调用.notify()时,AngularJS将看到至少有一个回调被注册,并且它将在下一个tick中安排刷新待处理队列(通过$ rootScope。$ evalAsync()实现)。这允许我们的控制器获得缓存数据的通知,即使它在调用.notify()之后绑定到notify事件。
为了看到这一点,我创建了一个通过两种不同方法返回数据的friendService。这两种方法都试图通过.notify()然后" live"返回缓存数据。数据通过.resolve()。这两种方法的唯一区别是在调用.notify()之前将no-op绑定到notify事件。
<!doctype html>
<html ng-app="Demo">
<head>
<meta charset="utf-8" />
<title>
Forcing $q .notify() To Execute With A No-Op In AngularJS
</title>
<link rel="stylesheet" type="text/css" href="./demo.css"></link>
</head>
<body ng-controller="AppController">
<h1>
Forcing $q .notify() To Execute With A No-Op In AngularJS
</h1>
<h2>
Friends
</h2>
<div ng-switch="isLoading">
<!-- Show while friends are being loaded. -->
<p ng-switch-when="true">
<em>Loading...</em>
</p>
<!-- Show once the friends have loaded and are available in the view-model. -->
<ul ng-switch-when="false">
<li ng-repeat="friend in friends track by friend.id">
{{ friend.name }}
</li>
</ul>
</div>
<p>
<a ng-click="load()">Load</a>
|
<a ng-click="loadWithNoop()">Load With No-Op</a>
</p>
<!-- Load scripts. -->
<script type="text/javascript" src="../../vendor/angularjs/angular-1.3.13.min.js"></script>
<script type="text/javascript">
// Create an application module for our demo.
var app = angular.module( "Demo", [] );
// -------------------------------------------------- //
// -------------------------------------------------- //
// I control the root of the application.
app.controller(
"AppController",
function( $scope, friendService ) {
$scope.isLoading = false;
$scope.friends = [];
// Load the friend data (defaults to "get" method vs. "getWithNoop").
loadRemoteData();
// ---
// PUBLIC METHODS.
// ---
// I reload the list of friends using friendService.get().
$scope.load = function() {
loadRemoteData( "get" );
};
// I reload the list of friends using friendService.getWithNoop().
$scope.loadWithNoop = function() {
loadRemoteData( "getWithNoop" );
};
// ---
// PRIVATE METHODS.
// ---
// I load the friends from the friend repository. I am passing-in the
// method name to demonstrate that, from the Controller's point-of-view,
// nothing here is different other than the name of the method. The real
// substantive difference exists in the implementation of the friend-
// Service method and how it interacts with $q / Deferred.
function loadRemoteData( loadingMethod ) {
console.info( "Loading friends with [", loadingMethod, "]" );
// Indicate that we are in the loading phase.
$scope.isLoading = true;
// When we make the request, we expect the service to try to use
// cached-data, which it will make available via the "notify" event
// handler on the promise. As such, we're going to wire up the same
// event handler to both the "resolve" and the "notify" callbacks.
friendService[ loadingMethod || "get" ]
.call( friendService )
.then(
handleResolve, // Resolve.
null,
handleResolve // Notify.
)
;
function handleResolve( friends ) {
// Indicate that the data is no longer being loaded.
$scope.isLoading = false;
$scope.friends = friends;
console.log( "Friends loaded successfully at", ( new Date() ).getTime() );
}
}
}
);
// -------------------------------------------------- //
// -------------------------------------------------- //
// I provide access to the friend repository.
app.factory(
"friendService",
function( $q, $timeout ) {
// Our friend "repository".
var friends = [
{
id: 1,
name: "Tricia"
},
{
id: 2,
name: "Heather"
},
{
id: 3,
name: "Kim"
}
];
// Return the public API.
return({
get: get,
getWithNoop: getWithNoop
});
// ---
// PUBLIC METHODS.
// ---
// I return the list of friends. If the friends are cached locally, the
// cached collection will be exposed via the promise' .notify() event.
function get() {
var deferred = $q.defer();
// Notify the calling context with the cached data.
deferred.notify( angular.copy( friends ) );
$timeout(
function networkLatency() {
deferred.resolve( angular.copy( friends ) );
},
1000,
false // No need to trigger digest - $q will do that already.
);
return( deferred.promise );
}
// I return the list of friends. If the friends are cached locally, the
// cached collection will be exposed via the promise' .notify() event.
function getWithNoop() {
var deferred = $q.defer();
// -- BEGIN: Hack. ----------------------------------------------- //
// CAUTION: This is a work-around for an optimization in the way
// AngularJS implemented $q. When we go to invoke .notify(),
// AngularJS will ignore the event if there are no pending callbacks
// for the event. Since our calling context can't bind to .notify()
// until after we invoke .notify() here (and return the promise),
// AngularJS will ignore it. However, if we bind a No-Op (no
// operation) function to the .notify() event, AngularJS will
// schedule a flushing of the deferred queue in the "next tick,"
// which will give the calling context time to bind to .notify().
deferred.promise.then( null, null, angular.noop );
// -- END: Hack. ------------------------------------------------- //
// Notify the calling context with the cached data.
deferred.notify( angular.copy( friends ) );
$timeout(
function networkLatency() {
deferred.resolve( angular.copy( friends ) );
},
1000,
false // No need to trigger digest - $q will do that already.
);
return( deferred.promise );
}
}
);
</script>
</body>
</html>
正如您所看到的,控制器将相同的处理程序绑定到&#34;解决&#34;和&#34;通知&#34;承诺的事件。通过这种方式,它可以统一处理缓存的数据和实时数据。唯一的区别在于它调用的服务层方法 - get()与getWithNoop()。而且,如果我们几次调用.get()然后几次调用.getWithNoop(),我们可以在控制台中看到差异。
答案 2 :(得分:1)
我尝试重现您的问题here。看来,您无法直接在承诺上调用notify
,但必须进行$apply
调用。
另请参阅$q
here的文档。
引用示例中的确切行:
因为这个fn在事件循环的未来转向中执行异步,所以我们需要将代码包装到$ apply调用中,以便正确地观察模型更改。
你可以试试这个并改变你的代码:
deferred.notify(response); // should not work
resource.query(function (success) {
deferred.notify('Returning from resource'); // should work
console.log("success querying RESTful resource")
localStorageService.add("data", success);
deferred.resolve(success);
}, function(error) {
deferred.notify('caught error!'); //should also work
console.log("error occurred");
deferred.reject(response);
});