Angular $q Promise Gotchas

This is here so you can laugh at the first mistake.

Gotcha: resolve(), not return:

Spot the bug:

$q(function(resolve, reject) {
    if (someVarInScope) {
        return someFunctionThatReturnsAPromise();
    } else {
        reject("some error message");
    }
});

It’s probably easy, given the headline. Line 3 is in error. It should be:

        resolve(someFunctionThatReturnsAPromise());

“return” exits the function. resolve() fulfills the promise.

We all habitually return values, so I’m pretty certain other people make this error.

Yuks!

Nice Tool: echo the value of a promise

function promiseEcho(value) {
    return $q(function(res) { $log.log('promiseEcho', value); res(value); });
}

This function just echoes the value to the console, and passes it through. You can use it with .then() to see messages and .catch() to see errors.

somePromise.then(promiseEcho).catch(promiseEcho)

Gotcha: calling a service, REST API, or REST endpoint twice

I had fixed this with some horrible code-smelling polling solution, but there’s a much better way to prevent multiple identical requests: return the exact same promise each time.

This article on Stack Overflow explained it.

I’m not sure why, but “return _cache || _cache = $q(….);” didn’t work for me. JS complained about a bad assignment. So I wrote it the long and verbose way:

var cachedPromise = null;
function promiseSomething(value) {
    if (cachedPromise) return cachedPromise;
    cachedPromise = $q(...your promise function here...);
    return cachedPromise;
}

Nice Trick: Lightweight Permissions with Promises

Angular’s ngRoute has a feature where you can run a batch of promises before a view is created. This way, the data is available for the view. OdeToCode has an article about it.

You can also insert promises to perform some authorization.

....
resolve: {
    data: function(LoginService) {
          return LoginService.promiseLogin().then(DataService.getData);
    }
}
....

LoginService.promiseLogin() returns a promise that resolves to the logged-in user’s information. Maybe it has the userid. That gets passed to DataService.getData(), which retrieves the data, and then resolves to it. That gets assigned to $scope.$resolve.data.

If promiseLogin() calls its reject(), ngRoute will broadcast a ‘$routeChangeError’ event. You should have a handler that will forward to the login page. (More on this in a future article.)

There’s a big gotcha in this: if promiseLogin() doesn’t call reject(), then there will be no error! This is because $q.all() will reject only when one of the promises passed to it rejects. It will resolve when they all resolve. If one fails to resolve, the entire promise is unresolved.

You can look at the code from ng-route where it resolves the ‘resolve’ promises.

So, if you’re using $q(), remember to always end your code blocks with resove() or reject(), or return a promise object, but don’t let them exit or return a bare value.

You can write promise functions that check the route against your permissions database, and see if the given user is allowed to see a specific piece of data, and reject the user if they aren’t allowed.

Here’s some pseudocode demonstrating how straightfoward this would read. I hope to have a functional demo for another article:

data1: LoginServer.promiseLogin().then(DataService.permission('data1')).then(DataService.getData1),
data2: LoginServer.promiseLogin().then(DataService.permission('data2')).then(DataService.getData2),
no_data_at_all: LoginServer.promiseLogin().then(DataService.permission('anything'))

If either promise rejects, the entire route is rejected. Your data security is (somewhat) preserved. At the very least, the application won’t reach out to the server to request the data. The third line doesn’t return data at all. It’s just a permission; you could even test the route.

Additionally, permission() could get a permission table from the server, so the permissions aren’t coded into the application at all.