Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular combining parallel and chained requests with $http.then() and $q.all()

I have a rather complicated set of API calls to make and I'm attempting to do it as elegantly and performant as possible. I understand how to use the promise api of the $http service to chain requests, and how to use the $q service to make requests in parallel. But for this specific API workflow I need to do both.

Here is an example of the high-level API flow:

  • /dog/<dog_id>
    • /breed/<breed_id>
      • /food/<food_id>
  • /cat/<cat_id>
  • /turkey/<turkey_id>
  • /fish/<fish_id>

The first tier of requests all have known ids. However the <breed_id> required to make the /breed call must be parsed from the /dog response, and the <food_id> required to make the /food call must be parsed from the /breed response. So /dog, /breed, and /food all need to be chained. However /cat, /turkey, and /fish can be made in parallel with the entire /dog chain.

What I've got now (and it is working fine) are two separate sets of requests. How do I improve on this flow? Is there a way to combine the two stacks in a way that results in a single promise execution of .then()?

var dogId = '472053',
    catId = '840385',
    turkeyId = '240987',
    fishId = '510412';

var myData = {};

var firstSetComplete = false,
    secondSetComplete = false,
    returnData = function() {
        if (firstSetComplete && secondSetComplete) {
            console.log("myData.dog", myData.dog);
            console.log("myData.dog.breed", myData.dog.breed);
            console.log("myData.dog.food", myData.dog.food);
            console.log("myData.cat", myData.cat);
            console.log("myData.turkey", myData.turkey);
            console.log("myData.fish", myData.fish);
        }
    };

// first call set
$http.get('http://example.com/dog/' + dogId)
.then(function(response) {
    myData.dog = response.data;
    return $http.get('http://example.com/breed/' + response.data.breed_id);
})
.then(function(response) {
    myData.dog.breed = response.data;
    return $http.get('http://example.com/food/' + response.data.food_id);
})
.then(function(response) {
    myData.dog.food = response.data;

    firstSetComplete = true;
    returnData();
});

// second call set
$q.all([
    $http.get('http://example.com/cat/' + catId),
    $http.get('http://example.com/turkey/' + turkeyId),
    $http.get('http://example.com/fish/' + fishId)
])
.then(function(responses) {
    myData.cat = responses[0].data;
    myData.turkey = responses[1].data;
    myData.fish = responses[2].data;

    secondSetComplete = true;
    returnData();
});
like image 477
gregtzar Avatar asked Nov 27 '13 20:11

gregtzar


1 Answers

You can pass in the first chain like so:

$q.all([
    $http.get('http://example.com/cat/' + catId),
    $http.get('http://example.com/turkey/' + turkeyId),
    $http.get('http://example.com/fish/' + fishId),
    $http.get('http://example.com/dog/' + dogId)
    .then(function(response) {
         myData.dog = response.data;
         return $http.get('http://example.com/breed/' + response.data.breed_id);
     })
     .then(function(response) {
         myData.dog.breed = response.data;
         return $http.get('http://example.com/food/' + response.data.food_id);
     })
     .then(function(response) {
         myData.dog.food = response.data;
         return myData;
     })
])
.then(function(responses) {
    myData.cat = responses[0].data;
    myData.turkey = responses[1].data;
    myData.fish = responses[2].data;

    secondSetComplete = true;
    returnData();
});

That big chain of promises for dogs ends up returning a single promise that is resolved when the last then is called and it's resolved with the result from that final function. So there is no reason that you can't nest it in your $q.all() call.

like image 199
Daniel Tabuenca Avatar answered Oct 13 '22 03:10

Daniel Tabuenca