Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detect existence of next handler in Angular JavaScript promise chain

Given the following two $resource examples:

var exampleOne = $resource('/path').save(objectOne);
exampleOne.$promise.then(function (success) {}, function (error) {});

var exampleTwo = $resource('/path').save(objectTwo);
exampleTwo.$promise.then(function (success) {});

[NOTE: Example two contains no error handler]

And an interceptor that sits below all $http requests:

var interceptor = ['$location', '$q', function ($location, $q) {
   function error(response) {
       if (response.status === 400) {
           return $q.reject(response);
       }
       else {
           $location.path('/error/page');
       }
       return $q.reject(response);
   }

   return {
       'responseError': error
   };
}

$httpProvider.interceptors.push(interceptor);

How can I make the interceptor not reject when the example resources $promise.then() contain no error callback? If the call back exists as in exampleOne then I wish to reject, but if not as in exampleTwo then I wish to redirect to the error page thus changing the conditional to something like:

if (response.status === 400 && $q.unresolvedPromises.doIndeedExist()) { ...

Why? Because only some situations in my project call for handling a 400 in a user friendly way, thus I'd like to eliminate many duplicate error callbacks or having to place a list of uncommon situations in the interceptor. I'd like the interceptor to be able to decide based on the presence of another handler in the promise chain.

like image 772
Mike Trionfo Avatar asked Aug 13 '14 18:08

Mike Trionfo


1 Answers

Simply put it is impossible, you can't detect if someone will attach a handler in some point in the future just like you can't tell if when you throw in a function it will be caught on the outside or not. However, what you want done can be done.

It is not a 'noob question', and it is very fundamental:

 function foo()
    throw new Error(); // I want to know if whoever is calling `foo`
                       // handles this error
 }

First, what you can do

Simply put in the first case:

 exampleOne.$promise.then(function (success) {}, function (error) {});

What you get is a promise that is always fulfilled. However, in the second case the promise might be rejected. Handling a rejection with a rejection handler is like a catch in real code - once you handle it it is no longer rejected.

Personally, I would not use an interceptor here, but rather a resource-using pattern since that's more clear with intent, you can wrap it in a function so it won't need a scope but I like that idea less. Here is what I'd do

attempt(function(){
    return $resource('/path').save(objectTwo).$promise.
           then(function (success) {});
});

function attempt(fn){
    var res = fn();
    res.catch(function(err){
        // figure out what conditions you want here
        // if the promise is rejected. In your case check for http errors
        showModalScreen();
    }
    return res; // for chaining, catch handlers can still be added in the future, so
                // this only detects `catch` on the function passed directly so 
                // we keep composability
}

Now, a short proof that it can't be done

Let's prove it for fun.

Let's say we are given the code of a program M, we create a new promise p and replace every return statement in M andthrow statement in M with a return p.catch(function(){}) and also add a return p.catch(function(){}), now a handler will be added to p if and only if running M ever terminates. So in short - given code M we have constructed a way to see if it halts based on an existence of a solution to the problem of finding if catch is appended to p - so this problem is at least as hard as the halting problem.

like image 136
Benjamin Gruenbaum Avatar answered Oct 14 '22 16:10

Benjamin Gruenbaum