What is the difference between $promise and $q promises in angular1.x?
Asked Answered
V

2

9

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()?

Varuna answered 25/2, 2017 at 11:17 Comment(1)
Short answer: $promise is a property on the return value of certain operations which returns a promise for the result of that operation. $q is a service which provides some promise creation and manipulation mechanisms like $q.all and $q.resolve.Simonize
F
7

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 ...});
Flyte answered 25/2, 2017 at 13:22 Comment(1)
Thanks for this answer :)Varuna
C
7

$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.

Columbium answered 25/2, 2017 at 11:53 Comment(1)
This is really helpful. It must be obvious to you, but I used an array of $resource.$promise's inside $q.all() and it worked. Your answer helped me strengthen my conceptual understanding. Thanks :)Varuna
F
7

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 ...});
Flyte answered 25/2, 2017 at 13:22 Comment(1)
Thanks for this answer :)Varuna

© 2022 - 2024 — McMap. All rights reserved.