I am not able to understand the use of $q service in angular js. Can some one please give elaboration on this topic that
what is the $q service in angularjs? How can we use that
I think article i wrote about $q might help you.
Introduction to $q
$q is an angular define a service. It’s same as new Promise(). But $q takes things to the next level by enhancing additional feature that developers can use to perform complex tasks more simply.
This is a sample for creating promise using $q
angular.module("app",[])
.controller("ctrl",function($scope,$q){
var work = "resolve";
var promise = $q(function(resolve, reject) {
if (work === "resolve") {
resolve('response 1!');
} else {
reject('Oops... something went wrong');
}
});
promise.then(function(data) {
alert(data)
})
})
$q.defer()
$q.defer() return the instance of the promise constructor. Once you create a defer object there are following methods and properties that you can access from that object
resolve(value) – resolves the derived promise with the value. If the value is a rejection constructed via $q.reject, the promise will be rejected instead.
reject(reason) – rejects the derived promise with the reason. This is equivalent to resolving it with a rejection constructed via $q.reject.
notify(value) - provides updates on the status of the promise's execution. This may be called multiple times before the promise is either resolved or rejected.
promise – {Promise} – promise object associated with this deferred
Conclusion
Use $q for constructing promises from non-promise Objects/callbacks, and utilize $q.all() and $q.race() to work with existing promises.
Related
This is a very common question, but I have never found the answer that works properly. I have come across three answers, but none always works.
$apply: This will force an update, but will randomly through an error if it gets called while a digest is in progress.
"safe apply" where there is a check for a digest in progress before calling $apply. This doesn't always update the view for some reason I haven't been able to determine. In addition, there is a small chance that a digest will start between the check and $apply.
$timeout: according to the documentation, this should work reliably but doesn't seem to always update the view.
from $timeout documentation, the parameter invokeApply:
If set to false skips model dirty checking, otherwise will invoke fn within the $apply block. (default: true)
It never throughs an error, but sometimes doesn't update the view during a page load.
Here is a code sample where the problem occurs during page initialization:
EditService.getEvents(Gparams.curPersonID)
.then(function successCallback(response) {
if (response.status=='200') {
do some stuff
} else {
handle an error
}
var timer = $timeout(function() { })
.then(function successCallback(response) {
do something
});
$scope.$on("$destroy", function(event {
$timeout.cancel(timer)});
}); });
What is the correct answer? Please don't just say what but also discuss why.
Here is a code sample where the problem occurs during page initialization
A common cause of .then methods not updating the DOM is that the promise is not an AngularJS $q service promise. The solution is to convert the suspect promise to a $q service promise with the $q.when method.
//EditService.getEvents(Gparams.curPersonID)
//CONVERT to $q service promise
$q.when(EditService.getEvents(Gparams.curPersonID))
.then(function successCallback(response) {
if (response.status=='200') {
do some stuff
} else {
handle an error
}
The .then method of a $q service promise is integrated with the AngularJS framework and its digest cycle. Changes to the scope model will automatically update the DOM.
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.
--AngularJS $q Service API Reference - $q.when
I started using promises in angular for resolving my api calls with the following syntax:
$scope.module = moduleFactory.get({id: $stateParams.id})
.$promise.then(function(response){
$scope.module = response;
}
Now, I have encountered a situation where I have to chain multiple promises in a for loop and execute some code once all the promises in the for loop have been resolved. I have been trying to search for how to do this with the $promise syntax, but most sources on the internet talk about $q. I am new into development work and am finding it very confusing to juggle between these two concepts ($q and $promise). Request you nice folks to: first, explain to me the difference between $promise and $q; second, if I decide to use $q for solving my present problem as described above, does it mean I will have to rewrite the code that used $promise in order to make it chainable with something like $q.all()?
You can construct a promise in angular by calling $q:
let promise = $q(function(resolve, reject) { ... };
or if you simply want a promise that resolves immediately:
let promise = $q.resolve(somevalue);
There is also an older way using $q.defer() to construct a deferred object and returning it's .promise attribute, but you should probably avoid doing it that way and consider it just for backward compatibility.
The final way to create a new promise is to call .then() (or .catch()) on an existing promise.
The .$promise attribute is simply a promise created by one of the above mechanisms either by the $resource service or by something following the same pattern.
Once you have some promises you can stuff them all into an array and use $q.all() to wait for them all to complete, or for one to reject. Or if you want things to happen sequentially you can chain them together by performing each step in the .then() of the previous promise:
let promise = $q.resolve();
for(... something ...) {
promise = promise.then(() => { ... next step here ...});
}
promise.then(() => { ... runs when everything completed ...});
That will execute everything in sequence whereas $q.all() starts them off in parallel. Sometimes you want one, sometimes the other:
let promises = [];
for(... something ...) {
promises.push(somethingThatReturnsAPromise());
}
$q.all(promises).then(() => { ... runs when everything completed ...});
$promise is a property of objects returned by the $resource Service class-type action methods.
It is important to realize that invoking a $resource object method immediately returns an empty reference (object or array depending on isArray). Once the data is returned from the server the existing reference is populated with the actual data.
The Resource instances and collections have these additional properties:
$promise: the promise of the original server interaction that created this instance or collection.
On success, the promise is resolved with the same resource instance or collection object, updated with data from server. This makes it easy to use in resolve section of $routeProvider.when() to defer view rendering until the resource(s) are loaded.
On failure, the promise is rejected with the http response object, without the resource property.
--AngularJS $resource Service API Reference
Note: The example code in the question is redundant and unnecessary.
$scope.module = moduleFactory.get({id: $stateParams.id})
.$promise.then(function(response){
//REDUNDANT, not necessary
//$scope.module = response;
});
The assignment of resolved responses to $scope is not necesssary as the $resource will automatically populate the reference when the results come from the server. Use the $promise property only when code needs to work with results after they come from the server.
To distinguish services which return $resource Service objects from other services which return promises, look for a .then method. If the object has a .then method, it is a promise. If it has a $promise property, it follows the ngResource pattern.
It must be obvious to you, but I used an array of $resource.$promise's inside $q.all() and it worked.
$q.all works with promises from any source. Under the hood, it uses $q.when to convert values or promises (any then-able object) to $q Service promises.
What sets $q.all apart from the all method in other promise libraries is that in addition to working with arrays, it works with JavaScript objects that have properties that are promises. One can make a hash (associative array) of promises and use $q.all to resolve it.
var resourceArray = resourceService.query(example);
var hashPromise = resourceArray.$promise.then(function(rArray) {
promiseHash = {};
angular.forEach(rArray,function (r) {
var item = resourceService.get(r.itemName);
promiseHash[r.itemName] = item.$promise;
});
//RETURN q.all promise to chain
return $q.all(promiseHash);
});
hashPromise.then(function (itemHash) {
console.log(itemHash);
//Do more work here
});
The above example creates a hash of items indexed by itemName with all the items being fetched asynchronously from a $resource Service.
I'm designing an API client with angular and the $http service using a Hypermedia API. Since I have resources that build on top of each other I wanted the client user to be able to do this declaratively:
apiRoot
.getPeopleRoot()
.getPerson(1)
.addJob(apiRoot.getJobsRoot().getRandomJob())
.updatePerson()
But I want to also be able to tap in $then methods to use intermediate results:
$scope.job = apiRoot.getJobsRoot().getRandomJob()
apiRoot.getPeopleRoot().getPerson(1).then(function (person) {
$scope.person = person
}).addJob($scope.job).updatePerson()
I managed to do each one of these separately by returning the promise or an object that contains the functions to operate with each of these resouces but I'm not sure how to achieve both at the same time, or if it's a good idea.
EDIT: I managed to progress using the following mixin to my resource objects
function Actualizable() {}
Actualizable.prototype.actual = function (value) {
this.realizedValue = value
return this
}
Then checking if I've resolved the value on each function or calling a promise to actually resolve it. I'm not sure if there's a mechanism to do this with promises still.
To chain promises, return values (or another promise).
apiRoot.getPeopleRoot().getPerson(1).then(function (person) {
$scope.person = person;
//return the value to chain
return person;
})
Because calling the .then method of a promise returns a new derived promise, it is easily possible to create a chain of promises
It is possible to create chains of any length and since a promise can be resolved with another promise (which will defer its resolution further), it is possible to pause/defer resolution of the promises at any point in the chain.
-- AngularJS $q Service API Reference -- Chaining Promises
Also see Angular execution order with $q
Given an unresolved deferred (dfd), and a then-able promise (promise), which may or may not be deferred, is there a way to 'proxy' the promise into the deferred?
The semantics should be as so:
promise.then(dfd.resolve, dfd.reject);
The $q documentation only mentions handling of rejected promises (and furthermore, only promises rejected in a certain way):
defered.resolve(value) – resolves the derived promise with the value. If the value is a rejection constructed via $q.reject, the promise will be rejected instead.
This makes it unclear if dfd.resolve(promise) is valid/supported. Also, I cannot use $q.when (which does take a then-able) because the promise of the defered has already been returned.
The Angular version is 1.2.x.
Yes, deferred.resolve takes a promise.
deferred.resolve(value)
Calling resolve with a pending promise causes promise to wait on the passed promise, becoming fulfilled with its fulfillment value or rejected with its rejection reason (or staying pending forever, if the passed promise does).
Calling resolve with a rejected promise causes promise to be rejected with the passed promise's rejection reason.
Calling resolve with a fulfilled promise causes promise to be fulfilled with the passed promise's fulfillment value.
Calling resolve with a non-promise value causes promise to be fulfilled with that value.
From the Q API Reference
To answer another part of your question:
""This makes it unclear if dfd.resolve(promise) is valid/supported. Also, I cannot use $q.when (which does take a then-able) because the promise of the defered has already been returned.""
The promise created by deferred.promise can be given to more that one recipient. Each recipient can call the .then method on that promise. The promise can only be resolved once (either to a value or an error). But the container can be read by more that one consumer.
Think of a promise as a container for a value which you can get in the future by the .then, .catch, and .finally methods. You can visit the container more than once but its contents are immutable when it resolves.
Deprecation of the .success and .error methods in the $http service
The AngularJS team in their new found wisdom have decided to deprecate the .success and .error methods. Those methods were buggy and I say good riddance.
For more information on the deprecation (or should I say failure) of the .success and .error methods visit the latest AngularJS $http Service API Docs.
We should avoid the .success and .error methods and learn to use the .then, .catch, and .finally from now on.
The $q service reference cited by the OP is dated. For the latest version, visit AngularJS $q Service API Docs.
UPDATE for Legacy AngularJS v1.2
I did some spelunking in the AngularJS Github. The legacy $http service creates a $q promise (L750) and subsequently attaches the buggy .success method (L769) and the buggy .error method (L776).
What this means is that people stuck with using the older version of AngularJS can start migrating to the .then, .catch, and .finally methods.
An example with two consumers of the same $http promise.
//Producer
var httpPromise = $http.get(url);
//Consumer #1
httpPromise.then (function (response) {
vm1.data = response.data;
}) .catch (function (err) {
//check for error
});
//Consumer #2
httpPromise.then (function (response) {
vm2.data = response.data;
}) .catch (function (err) {
//check for error
});
Notice that the .then method returns data differently than the .success method.
Also both consumers should check for errors.
So even users of legacy AngularJS can start writing .success and .error free code.
Yes.
Any time you resolve a promise to another promise (whether by resolve() or by returning from a callback), it will implicitly wait for that other promise.
In fact, it is impossible to make a promise actually resolve to another promise instance (without waiting).
What is the difference between defer object promise and promise from $resource service?
I know, that in some cases one uses $q service to create deferer, then resolve response and return promise.
Others in the same time might do something like return $resource(...).get().$promise;.
What is the diff. between those two approaches?
The promise returned from $resource is one someone initially used $q.defer() (or the newer more modern promise constructor) to create.
That someone is the $http service used internally inside $resource - you are using a promise they created for you.
Typically, you only need to use a $q.defer or the promise constructor at the lowest level of your code when working with async - otherwise you're typically better off using promise chaining. Otherwise you end up with the explicit construction anti-pattern.