14

I am currently using $q service from angular to make API calls like this:

var deferred = $q.defer();
$http.get(config.apiHost + details.url)
    .success(function (data) {
        deferred.resolve(data);
    }).error(function (msg) {
        deferred.reject(msg);
    });
return deferred.promise;

but we can also use this approach also without using $q:

return $http.get(config.apiHost + details.url)
    .success(function (data) {
        return data;
    }).error(function (msg) {
        return msg;
    });

and as $http itself returns the promise, I can also use more simplified approach:

$http.get(config.apiHost + 'posts')
        .success(function (data) {
            console.log(data)
        }).error(function (msg) {
            console.log(msg);
        });

So what is the difference between all these specially between $q and $http, as both returns promise and both are async ? Does angular provides some additional functionality with $q ? I am not able to find any good answer.

georgeawg
  • 48,608
  • 13
  • 72
  • 95
Bhushan Goel
  • 2,114
  • 3
  • 19
  • 31
  • You can return the promise with other asynchronous operations with $q. $http returns a promise from AJAX calls. – Hoyen Jun 28 '16 at 15:22
  • other operations like ? – Bhushan Goel Jun 28 '16 at 15:23
  • Like reading files or if you need to use $timeout – Hoyen Jun 28 '16 at 15:24
  • `$q` is mainly only used for compatibility with libraries that don't support promises by default *and* when you can't rely on a native implementation of Promise. There's no reason (for you) to use it otherwise. An example would if you wanted to make a promise-based `$timeout`. – Dan Jun 28 '16 at 15:24
  • Ok, that means for API calls we can simply use $http instead of $q as they both returns promise. $q is useful only when some libraries do not support promises natively. – Bhushan Goel Jun 28 '16 at 15:26
  • @BhushanGoel yes, I suppose that would be the simplest way to explain it – Hoyen Jun 28 '16 at 15:28

3 Answers3

9

$http uses $q, the first example is redundant, and so is the second. You just need to return the promise that $http.get returns:

return $http.get(config.apiHost + details.url);

The above is the same as your first piece of code.

In addition, return msg is not the same as deferred.reject(msg). The equivalent would be throw msg or return $q.reject(msg)

Another thing to note is that success and error are non-standard, you want to use then and catch.

elclanrs
  • 92,861
  • 21
  • 134
  • 171
  • I wouldn't say that `success` and `error` are "non-standard", just deprecated: https://docs.angularjs.org/api/ng/service/$http – pulse0ne Jun 28 '16 at 15:33
  • 1
    @pulse0ne they *are* non-standard if you're talking about the A+ promise spec – Dan Jun 28 '16 at 15:41
  • @DanPantry ah, yes, I was mistakenly thinking you were referring to the angular $http service. In regards to the spec, it is non-standard and almost certainly why the angular folks have deprecated it. – pulse0ne Jun 28 '16 at 15:49
4

$q is mainly only used for compatibility with libraries that don't support promises by default and when you can't rely on a native implementation of Promise (for example - in older browsers like IE9). There's no reason (for you) to use it otherwise. An example would if you wanted to make a promise-based $timeout. $http itself uses $q under the hood for these exact reasons.

Unlike what other (since deleted) answers have suggested, you do not need to use $q in order to "store" the result of the $http promise. I would not recommend storing the promise at all (as this tends to lead to spaghetti code), but if you must absolutely do this, you can just store the resultant promise from $http; promises only ever execute once.

Any functions passed to then after a promise has resolved/rejected will be resolved on the next tick, without re-invoking the original action that created the promise in the first place - IOW, the result of the promise is memoized within that object.

Also note that promises chain, which is abit out of scope for this answer, but it essentially means that the following pieces of code are equivalent

function legacyGet() {
  const deferred = $q.defer()
  $http.get('http://www.google.com')
    .then((response) => deferred.resolve(Object.assign(response, {foo: bar}))
    .catch((error) => deferred.reject(error))
  return deferred.defer
}

function modernGet() {
  return $http.get('http://www.google.com')
    .then((response) => Object.assign(response, {foo: bar}))
}

To summarise: Your title is wrong. We don't prefer using $q, we only use it sparingly. Prefer to use an ES6 Promise unless you need to support browsers that don't have it and you can't use a polyfill.

Dan
  • 10,282
  • 2
  • 37
  • 64
0

In angular mostly all the services returns promises only, but there are some instances where you would like to create your own deferred object using $q.

Case 1

When you are using a library which does not support promise or you have created your own function and want to return a promise.

Case 2

When you are using any construct which by default returns a promise but you want to return a separate promise based on some on some condition.

Example: In angular $http returns a promise only but now if you want that if the response of this promise contains a particular value then only you want to return resolved else return failure then you need to create your own deffered object and need to resolve or fail it based on the value returned by $http response.

Rishi Tiwari
  • 1,041
  • 1
  • 10
  • 20
  • You do not need to do that. Promises chain. You can just as easily do `$http.get().then(...)` and the value returned from `then` will be passed to the next .then in the chain. No need to use your own deferred object for this. – Dan Jun 28 '16 at 15:36
  • I'm talking about the response of the $http..like if you want that if the response contains a certain value than only resolve the promise or just reject. – Rishi Tiwari Jun 28 '16 at 15:43
  • 1
    This answer is inaccurate and is describing the common deferred anti-pattern – charlietfl Jun 28 '16 at 15:43
  • If you're suggesting you'd want to reject a promise based on the value of the response, yes, I'll grant you - that is one case where you would want to use `$q` (but you could still return `$q.reject()` instead of using the deferred antipattern)... but that seems like an incredibly narrow use-case. – Dan Jun 28 '16 at 15:44
  • But it is a case right....otherwise angular services already return promises..why would you use deferred object over them. – Rishi Tiwari Jun 28 '16 at 15:46
  • It's not really a case, no, as I said - you could just as easily use `$q.reject()` and return that as a result of a promise. And as mentioned in my answer, the *only* use case for the deferred pattern is when you can't support an ES6 Promise and the method you're working with doesn't return one – Dan Jun 28 '16 at 15:47
  • In your answer also you are doing the same thing... you are tweaking the response only...If this tweaking is not required then why would you use $q for that..simple $http would do.. – Rishi Tiwari Jun 28 '16 at 15:50