Reputation: 472
I have 2 plunkr's ready to demonstrate what I'm confused about. I am seeking a clear explanation as to precisely why the first plunkr fails, yet the second works.
In this first plunkr, I simulate calling into an external library to do some kind of networked authentication. The problem is, when there are 2 promises in the chain from the original controller level method, the function passed to the first promise for execution upon resolve never fires and neither do any other promises further down the chain despite resolving each promise.
http://plnkr.co/edit/6uKnVvEI3bJvfmaUoWN0
However, when I change the calls to use a $timeout, regardless of whether it is used to simulate a delay, or just to wrap a deferred.resolve that comes back from an actual external operation (like calling a REST API), everything works as expected. In this second plunkr, you can see the login functionality working just fine once both deferred.resolve calls have been modified to be wrapped in a $timeout call.
Additionally, I included a test case that someone else suggested as the problem where resolving a promise before returning it would fail. This is clearly not the case as this second plunkr works just fine when doing precisely that. Note that this alternate method does not use a $timeout but still works just fine. This indicates to me that there is something special about the relationship between the two services (testApi, authService) when both return promise objects resulting in a chain of nested promises that have to resolve upwards.
http://plnkr.co/edit/xp8NeZKWDep6cPys5gJu?p=preview
Is anyone able to explain to me why these promises fail in one instance but work in another when they are either not nested, or if nested wrapped in a $timeout?
My hunch is something related to the digest cycle, but for the life of me I cannot understand why the digest cycle would affect services running essentially independent of a controller. They are not properties on the $scope that need to resolve before the controller loads, but functions wrapping service calls that return a promise.
Upvotes: 1
Views: 1984
Reputation: 14114
Your hunch is right. When you use setTimeout
, it triggers an event Angular knows nothing about; that's not the case if you use $timeout
instead. So, for your first Plunker script to work, you need to start a digest cycle manually by calling $rootScope.$apply()
:
angular.module('testApi', []).factory('testApi', function($q, $rootScope) {
...
setTimeout(function() {
$rootScope.$apply(function() {
deferred.resolve({user: { id: '1', name: 'bozo'}});
});
}, 1000);
Plunker here.
You won't need to do any of the above if you stick with $timeout
, and I suggest that you do that.
This SO question has more info on why promises callbacks are only called upon entering a $digest cycle.
Upvotes: 3