Chain Angular $http calls properly?
Asked Answered
I

3

17

I have been reading about $q and promises for days now and I seem to understand it...somewhat. I have the following situation in practice:

  1. An $http request is made and checks whether a subsequent call can be made.
  2. If the first call fails, return "no data", if it succeeds and says a call can be made, the second call is made, if not - "no data" again. If the second call succeeds, it returns data, if not - "no data". It looks like this (approximately, I simplified for general idea, so don't worry about the minor mistakes here):

            return $http.get (something)
                .then(function(allowedAccess){
                    if(allowedAccess){
                        return $http.get (somethingElse)
                            .then( function(result){return {data:result} },
                            function(error){return {data:"n0pe"} }
                        )
                    } else {
                        return {data:"n0pe"}
                    }
                },
                function(){ return {data:"n0pe"} });
    

I was told to use $q here. I don't really understand how or why I would. The $http calls are promises already.

If there is a way to make this cleaner, I don't see it. Just got done re-reading this post on the subject. Essentially, am I missing something / is there a better way to do this?

Edit: Also just re-read a tutorial on chaining promises - it doesn't handle call failures at all. Basically posting this as due diligence.

Edit 2: This is more of an elaborate on the theory I am asking about, excerpt from the first article:

This is a simple example though. It becomes really powerful if your then() callback returns another promise. In that case, the next then() will only be executed once that promise resolves. This pattern can be used for serial HTTP requests, for example (where a request depends on the result of a previous one):

This seems to be talking about chains like this:

   asyncFn1(1)
    .then(function(data){return asyncFn2(data)})
    .then(function(data){return asyncFn3(data)})

So, if I understand correctly this a). Doesn't apply to me because I don't have a 3rd function. b). Would apply to me if I had three functions because while I run an if statement inside the first $http request, and only inside the if statement do I return another promise. So, theoretically, if I had three async functions to chain, I would need to put my if statement inside a promise?

Inoculate answered 13/9, 2015 at 4:19 Comment(1)
C
14

Promises really help with code composition of making async calls. In other words, they allow you to compose your code in a similar manner to how you would compose a synchronous set of calls (with the use of chained .thens) and as if it the sync code was in a try/catch block (with .catch).

So, imagine that your HTTP calls were blocking - the logic you have would look like so:

var allowedAccess, data;
try {
  allowedAccess = $http.get(something);

  if (allowedAccess){
    try{
      var result = $http.get(somethingElse);
      data = {data: result};
    } catch (){
      data = {data: "n0pe"};
    }
  } else {
    data = {data: "n0pe"};
  }
} catch (){
  data = {data: "n0pe"};
}
return data;

You could simplify it a bit:

var allowedAccess, result;
try {
  allowedAccess = $http.get(something);
  var result;
  if (allowedAccess) {
     result = $http.get(somethingElse);
  } else {
     throw;
  }
  data = {data: result};
} catch () {
   data = {data: "n0pe"};
}
return data;

And that would translate to the async version of:

return $http
          .get(something)
          .then(function(allowedAccess){
             if (allowedAccess){
               return $http.get(somethingElse);
             } else {
               return $q.reject(); // this is the "throw;" from above
             }
          })
          .then(function(result){
             return {data: result};
          })
          .catch(function(){
             return {data: "n0pe"};
          })

At least, this is the reasoning you could apply when composing code with branches and async calls.

I'm not saying that the version I presented is optimal or shorter - it is, however, more DRY because of a single error handling. But just realize that when you do .then(success, error) it is equivalent to try/catch over the previous async operation - this may or may not be needed depending on your specific circumstance.

Classify answered 13/9, 2015 at 5:26 Comment(3)
Thank you very much for your detailed answer, still processing it, will come back to accept yours or Matt's tomorrow.Inoculate
What are we rejecting with $q.reject() here? I am familiar with using reject() on a deferred created with $q.defer(). Here it's just rejecting the second $http.call? But isn't the if statement not being a promise a problem? Most likely I am just missing something.Inoculate
@VSO, $q.reject is equivalent to throw in the sync-world. It's a rejected promise, so it will end up in the .catch rather than the next .thenClassify
K
8

This is how I would code this sort of problem:

// returns a promise that resolves some endpoint if allowed
function getDataWithAccess(allowed){
    return allowed ? $http.get(someEndpoint) : $q.reject();
}

// do something with data
function handleData(data){
    // do stuff with your data
}

// main chain
$http.get(accessCredEndpoint)
    .then(getDataWithAccess)
    .then(handleData)
    .catch(function(err){
        return { data: "n0pe" };
    });

Yes, this is very much like New Dev's answer, however I wanted to make a point of extracting the functions into their own blocks. This makes the overall code much more readable.

Katherinkatherina answered 13/9, 2015 at 5:44 Comment(1)
Thank you for your reply, looks like I need some time to digest it, will come back to accept either your or New Dev's answer after some sleep.Inoculate
J
-1

$q will help reduce pyramid of calls like this:

async-call1.then(...
  aysnc-call2.then(...

This blog post - http://chariotsolutions.com/blog/post/angularjs-corner-using-promises-q-handle-asynchronous-calls/ - offers a clean way of making multiple HTTP requests. Notice the cleaner approach using $q. In case you were hitting a single HTTP endpoint, using your method would have been just fine. I'd say, what you have is fine also; $q might allow greater flexibility in the future.

The blog post describes a service while using $q and the code looks cleaner.

service('asyncService', function($http, $q) {
  return {
    loadDataFromUrls: function(urls) {
      var deferred = $q.defer();
      var urlCalls = [];
      angular.forEach(urls, function(url) {
        urlCalls.push($http.get(url.url));
      });
      // they may, in fact, all be done, but this
      // executes the callbacks in then, once they are
      // completely finished.
      $q.all(urlCalls)
      .then(...

I am a beginner with promises also, so take this with a grain of salt.

Jenaejenda answered 13/9, 2015 at 4:54 Comment(1)
The OP's question clearly requires sequential calls, since a second call depends on the return parameter of the first. $q.all works when the calls are independent of each otherClassify

© 2022 - 2024 — McMap. All rights reserved.