I am following the help on AngularJS's documentation for Q's implementation $q. I tried out the following code from https://docs.angularjs.org/api/ng/service/$q
// for the purpose of this example let's assume that variables `$q`, `scope` and `okToGreet`
// are available in the current lexical scope (they could have been injected or passed in).
function asyncGreet(name) {
var deferred = $q.defer();
setTimeout(function() {
// since this fn executes async in a future turn of the event loop, we need to wrap
// our code into an $apply call so that the model changes are properly observed.
scope.$apply(function() {
deferred.notify('About to greet ' + name + '.');
if (okToGreet(name)) {
deferred.resolve('Hello, ' + name + '!');
} else {
deferred.reject('Greeting ' + name + ' is not allowed.');
}
});
}, 1000);
return deferred.promise;
}
var promise = asyncGreet('Robin Hood');
promise.then(function(greeting) {
alert('Success: ' + greeting);
}, function(reason) {
alert('Failed: ' + reason);
}, function(update) {
alert('Got notification: ' + update);
});
My understanding is that the $scope.apply here is to give the callback Angular's context and make sure variables under $scope are accessible.
But below on comparing $q and Kris Kowal's Q, the test code goes:
it('should simulate promise', inject(function($q, $rootScope) {
var deferred = $q.defer();
var promise = deferred.promise;
var resolvedValue;
promise.then(function(value) { resolvedValue = value; });
expect(resolvedValue).toBeUndefined();
// Simulate resolving of promise
deferred.resolve(123);
// Note that the 'then' function does not get called synchronously.
// This is because we want the promise API to always be async, whether or not
// it got called synchronously or asynchronously.
expect(resolvedValue).toBeUndefined(); // <= so the deferred is not resolved without the 'apply'?
// Propagate promise resolution to 'then' functions using $apply().
$rootScope.$apply();
expect(resolvedValue).toEqual(123);
}));
It says $rootScope.$apply() is to propagate the promise resolution to 'then'. I am confused there... So without using apply, the deferred.resolve will not actually resolve?
This documentation sucks.
So the thing with $q
is that when your promise is resolved (and also presumably on reject or notify), it invokes your handlers within $rootScope.evalAsync
which ensures that after invocation, it'll trigger a digest and thus the rest of your application can have a chance to update or otherwise respond to the changes, just the way we like it.
As you found out, it works just fine without the explicit $apply
in an example app. However, the reason why they are doing explicit $apply
is because that automagic with $evalAsync
doesn't get a chance to work when running synchronously in a test, not because it's required for your application to Just Work ™.
With a few other notable services that are augmented for testing in angular-mock.js
, like $http
and $timeout
, we can explicitly flush when we want to simulate an async http request/response or a timeout (for example). The equivalent to stuff waiting to be eval
ed is to trigger a digest, which will get your promise handler invoked in the proper context. This is done with $apply
or $digest
, and hence why you're seeing it in their examples... because their examples are written as synchronous tests.
The docs should explain the difference between what you need to do to get your tests working and what your application itself should focus on to get the job done. The docs have a bad habit of making test facts their examples, and it just confuses people.
Hope that helps.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With