$q.when() Is The Missing $q.resolve() Method In AngularJS
In AngularJS, the $q services has a top-level method, $q.reject(), which will create a promise that is immediately rejected with the given value. Ever since I saw this method, I have always wondered where the "resolve" equivalent was? If there's a $q.reject(), why not a $q.resolve()? Well, it turns out there is. Thanks to a mental-block, I never quite made the connection; but, $q.when() is the "missing" $q.resolve() method.
Run this demo in my JavaScript Demos project on GitHub.
The $q.when() method doesn't just create a promise that is immediately resolved; rather, it normalizes a value that may or may not be a "thenable" object. If the given value is a promise, $q.when() will properly chain off of it. If the given value is not a promise, $q.when() will create promise resolved with the given value.
To see this in action, I've put together a tiny demo that showcases the "simple" case of $q.reject() and $q.when() to create immediately rejected and resolved promises, respectively:
<!doctype html>
<html ng-app="Demo">
<head>
<meta charset="utf-8" />
<title>
$q.when() Is The Missing $q.resolve() Method In AngularJS
</title>
</head>
<body ng-controller="AppController">
<h1>
$q.when() Is The Missing $q.resolve() Method In AngularJS
</h1>
<!-- Load scripts. -->
<script type="text/javascript" src="../../vendor/angularjs/angular-1.2.26.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, $q ) {
// The $q.reject() method creates a promise that is immediately rejected
// with the given reason.
$q.reject( "meh" ).catch(
function handleReject( reason ) {
console.log( "Rejected with reason:", reason );
}
);
// The $q.when() method creates a promise that is immediately resolved
// with the given value.
// --
// NOTE: If the given value is already a promise then it will be properly
// chained (and routed) by the resultant promise.
$q.when( "woot!" ).then(
function handleResolve( value ) {
console.log( "Resolved with value:", value );
}
);
}
);
</script>
</body>
</html>
When we run this code, we get the following console output:
Rejected with reason: meh
Resolved with value: woot!
As you can see, each method returned a promise. The $q.reject() promise was rejected and the $q.when() promise was resolved.
Want to use code from this post? Check out the license.
Reader Comments
Wow can't believe I never realized this. Thanks!
@Rob,
Right?! Staring me in the face for months :D
You are not alone!
It took me a lot of time to figure out the best way to use $q, specially inside services.
Before $q.when() and $q.reject() I was returning some silly responses into service methods =/
@Darlan,
Promises are generally hard to wrap your head around. And, even when you start using them, I find that I often forget how to use them properly. Every few months, I find it helpful to review this blog post on promise anti-patterns and how to avoid them:
http://taoofcode.net/promise-anti-patterns/
Some really good stuff there!
I thought the same thing when I first started using promises. In fact I went as far as writing $q.noop before discovering $q.when.
$q.noop = function () {
var deferred = $q.defer();
deferred.resolve();
return deferred.promise;
};
I do have one gripe about $q.when though. $q.when(myFunc) will resolve immediately myFunc. Although I think this is the clearest and cleanest API, it's not the most useful for me. Instead I prefer $q.whenFn(myFunc) which invokes and resolves the return from myFunc. $q.whenFn has proven really useful for implementing lazy loading in my APIs! I wrote about $q.whenFn here: http://www.codeducky.org/anqwhenfn/
@Steven,
There is actually no need in such noop() which doesn't accept any arguments since there is $q.resolve (not it's a property) which serves this purpose.
@Steven,
I think great minds think alike! I also had your concerns and wrote something similar:
www.bennadel.com/blog/2775-monkey-patching-the-q-service-using-provide-decorator-in-angularjs.htm
Good stuff!
I love the documentation for $q.when:
"Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. This is useful when you are dealing with an object that might or might not be a promise, or if the promise comes from a source that can't be trusted."
...specifically that last part
What exactly does this mean? If you're using a promise from an external library that uses setTimeout - would this be an example of a promise that "can't be trusted"? Or is it more that you can't trust if it's a promise or a value?
@Jordan,
That's a really good question! One guess could be that it has to do with the way $q integrates with the $digest lifecycle. If you create a promise with $q, AngularJS will automatically trigger a $digest whenever the promise is exercised, either with reject, resolve, or notify. If, however, you get a promise from a non-AngularJS source, then you don't have that digest-integration. Using $q.when() to wrap an "untrusted" promise would be able to bridge the gap and make sure that the $digest cycle is triggered when the original promise returns with a value.
But that is just a guess on my part - it's actually something that I'd like to try to play with.
@Ben,
Yes - exactly what I was thinking!
A good use case for this is if, for instance, you really want to use an external promise library - you want to avoid the "Forgotten Promise" anti-pattern, but the library gives you a way to declare a timeout so you can ensure your call is not forgotten.
Anyways I had a look at the code and it doesn't appear that it makes a difference whether you decide to manually create the promise via $q.defer() or use $q.when(). $q.when() appears to just be a convenience method so you don't have to write extra code:
var when = function(value, callback, errback, progressBack) {
var result = new Deferred();
result.resolve(value);
return result.promise.then(callback, errback, progressBack);
};
..and so I don't think the $digest cycle should be handled any differently.
Also, just thought I would share this fun post on Angular promises: http://andyshora.com/promises-angularjs-explained-as-cartoon.html
@Jordan,
I just put together a follow-up exploration of this "can't be trusted" concept:
www.bennadel.com/blog/2835-normalizing-untrusted-deferred-promise-values-for-the-digest-lifecycle-in-angularjs.htm
If you watch the video, you will see that using the raw jQuery Deferred value will not trigger a digest; but, once it is wrapped in $q.when(), the $digest is successfully triggered.
And yes, I believe that you are wright - the .when() method is for convenience.
good!
Thanks for this great short info video. I really wish they'd called it resolve and not when. When is conditional, resolve would just be that... resolve it. *sigh*
Yeah, they have added an alias for $q.when() called $q.resolve() :)
https://github.com/angular/angular.js/commit/3ef529806fef28b41ca4af86a330f39a95699cf6
@André,
Ha ha, I just popped over here to post the same exact thing. You beat me to it :D
Ben, thanks for pointing this out! The angular documentation is very sparse about this and I would not have drawn the same conclusion.
Just solved an immediate problem I was facing.
Cheers
Jürgen