Really?! You can do that with promises?!

We use promises all the time (especially on our girlfriends).

Code with heavy use of promises tends to get clumsy on occasions.

Usually there are elegant ways to do things with promises, but some programmers are just not aware of them.

I'll demonstrate via use cases.

The solutions are hidden so that you can ponder about them before seeing my suggestion.


Use Case #1

You're writing a data service that fetches orders from the server and stores them in memory.

'get' is a function that should fetch an order from the server by id, store it, and return it.

If it was already fetched, it should return it immediately.

Solution:
JS Bin

Explanation:
If we didn't fetch the order, we make an API call and cache the promise.

If we already fetched the order, we return the promise.

Note that then actually returns a new promise that will resolve to the return value of the callback.
This is called promise chaining.

When you call then, you implicitly create a new promise that is resolved to the callback's return value.

A discouraged approach is to manually create a deferred object using $q.defer().
That also works, it's just less readable.


Use Case #2

You have a complex page that shows data from several API end-points. You need to show a spinner until all data is rerieved from the server.

Solution:
JS Bin

Explanation:

We use $q's all method, that returns a new promise which is resolved when all given promises are resolved.
If one of the promises rejects, the error callback is immediately called.

A naive approach would be to chain the HTTP calls using then.
This effects performance, as it causes the AJAX calls to be made one by one and not in parallel.

$q.all(promises) - returns a combined promise

When passing an object to $q.all, instead of an array, the resolved value will also be an object, with corresponding keys.


Use Case #3

You're using a service that has 2 distinct functions:
isAdmin and isLoggedin. Both return a promise that resolves to a boolean.
You want to write 'isAdminLoggedin' that returns a promise that resolves to true only if both isAdmin and isLoggedin resolve to true.

Solution:
JS Bin

Explanation:

If isLoggedin resolves to false, we resolve to false. Otherwise - we resolve to whatever isAdmin resolves to.

Notice that in JS false && someVar will return false, and true && someVar will return someVar.

When calling then, you can return either a regular value, or a promise.

If you return a promise, the next 'then' in the chain will use the value that the promise resolves to.


Use Case #4

You want to wrap a $http call with a promise that resolves to response.data, or reject with response.error if it exists.

Solution:
JS Bin

Explanation:
We use $q.reject, instead of defining a deferred object.

$q.reject(value) => Returns a promise that is rejected with the given value.

$q.when(value) => Returns a promise that is resolved to the given value

Calling

$q.reject(reason)

is like calling:

deferred = $q.defer();  
deferred.reject(reason);  
return deferred.promise;  

But much shorter.

Side notes:

  • I prefer working with regular promises and not httpPromises that have success and error callbacks.
  • If you want to accomplish the above site-wide, you can use an interceptor.

Hope you enjoyed and learned new stuff!

* Thanks to Benjamin Gruenbaum for a great feedback