1
votes

Trying to get nested async REST requests in AngularJS (1.6), and then execute code when all requests have been completed.

I tried using $q.all but it seems this will not take into account the inner requests (the episode ones).

How do I modify the below example so that I can execute code when all requests have been completed?

// foreach series --> load series details & seasons, foreach season --> load episodes
// when all requests are completed: do something

var requests = []

// e.g. series = [1]
series.forEach(function(seriesId) {
    requests.push(loadSeriesDetails(seriesId).then(function(data) {
        console.log("received details for series "+seriesId)
        // e.g. {id:1, title:"show 1"}
    }))
    requests.push(loadSeasons(seriesId).then(function (data) {
        console.log("received seasons for series "+seriesId+": ", data.seasons.length)
        // e.g. [{id:11, title:"season 1"}, {id:12, title:"season 2"}]
        data.seasons.map(function(e) {return e.id}).forEach(function(seasonId) {
            requests.push(loadEpisodes(seasonId).then(function (data) {
                console.log("received episodes for season "+seasonId+": ", data.episodes.length)
                // e.g.
                // season 11: [{id:111, title:"episode 1-1-1"}, {id:112, title:"episode 1-1-2"}, {id:113, title:"episode 1-1-3"}]
                // season 12: [{id:121, title:"episode 1-2-1"}, {id:122, title:"episode 1-2-2"}]
            }))
        })
    }))
})

$q.all(requests).then(function(result) {
    console.log("*** all requests completed ***")
    console.log(result.length)
})

The example above would return 2 (1x loadSeriesDetails, 1x loadSeasons) instead of 4 (1x loadSeriesDetails, 1x loadSeasons, 2x loadEpisodes).

Suggestions? Thanks in advance!

Update: each of the is request functions is like:

loadSeriesDetails = function(id) {
  url = "..."+id

  return $http.get(url).then(
    function (result) {
      return result.data
    }, function (error) {
      ̶r̶e̶t̶u̶r̶n̶ ̶e̶r̶r̶o̶r̶ 
      throw error;
  });
}
2
JavaScript has an amazing thing called a return statement. To understand how to use it with promises, see You're Missing the Point of Promises. - georgeawg
Not sure I understand your comment. The promise functions have returns (I have updated the question to show an example of one). Don't think the .then(...) callback function needs a return. Do you have an example of what it should look like? - wivku
To avoid converting a rejected promise to a fulfulled promise, use a throw statement in the rejection handler. - georgeawg

2 Answers

3
votes

A variation of Frank's answer, which stays closer to the original code snippet.

// foreach series --> load series details & seasons, foreach season --> load episodes
// when all requests are completed: do something

var requests = []

// e.g. series = [1]
series.forEach(function(seriesId) {
    requests.push(loadSeriesDetails(seriesId).then(function(data) {
        console.log("received details for series "+seriesId)
        // e.g. {id:1, title:"show 1"}
    }))
    requests.push(loadSeasons(seriesId).then(function (data) {
        console.log("received seasons for series "+seriesId+": ", data.seasons.length)
        // e.g. [{id:11, title:"season 1"}, {id:12, title:"season 2"}]
        var innerRequests = [] // <<<==============
        data.seasons.map(function(e) {return e.id}).forEach(function(seasonId) {
            innerRequests.push(loadEpisodes(seasonId).then(function (data) {
                console.log("received episodes for season "+seasonId+": ", data.episodes.length)
                // e.g.
                // season 11: [{id:111, title:"episode 1-1-1"}, {id:112, title:"episode 1-1-2"}, {id:113, title:"episode 1-1-3"}]
                // season 12: [{id:121, title:"episode 1-2-1"}, {id:122, title:"episode 1-2-2"}]
            }))
        })
        return $q.all(innerRequests) //  <<<==============
    }))
})

$q.all(requests).then(function(result) {
    console.log("*** all requests completed ***")
    console.log(result.length)
})
2
votes

Your requests array should have all of the promises that need to be resolved when you pass it to $q.all. Currently you are adding your promises for loadEpisodes to requests after earlier promises resolve.

You can break this up so that the inner section returns its own $.q.all, which will become chained:

    var requests = [];

    series.forEach(function (seriesId) {
        requests.push(loadSeriesDetails(seriesId).then(function (data) {
            console.log("received details for series " + seriesId);
            return data;
        }));

        requests.push(loadSeasons(seriesId).then(function (data) {
            console.log("received seasons for series " + seriesId + ": ", data.seasons.length);
            return loadAllEpisodes(data.seasons);
        }));
    });

    function loadAllEpisodes(seasons) {
        var requests = seasons.map(function (season) {
            return loadEpisodes(season.id).then(function (data) {
                console.log("received episodes for season " + season.id + ": ", data.episodes.length);
                return data;
            });
        });

        return $q.all(requests);
    }

    q.all(requests).then(function(result) {
        console.log("*** all requests completed ***");
        console.log(result.length);
    });

Note that in your example you'll still only see 2 results in the final array that is returned from the outer $q.all, because you're getting the results from the last promise of each chain. So you may want to adjust your return statements so they group information the way you need it.