Angular $q returning promise multiple $http calls

10,263

Solution 1

$q.all and a map function are what you need here:

function getSearchData() {
    return {
        // returns a promise for an object like:
        // { abo: resultFromAbo, ser: resultFromSer, ... }
        loadDataFromUrls: function () {
            var apiList = ["abo", "ser", "vol", "con", "giv", "blo", "par"];

            return $q.all(apiList.map(function (item) {
                return $http({
                    method: 'GET',
                    url: '/api/' + item
                });
            }))
            .then(function (results) {
                var resultObj = {};
                results.forEach(function (val, i) {
                    resultObj[apiList[i]] = val.data;
                });
                return resultObj;        
            });
        }
    };
}

Solution 2

If you have an arbitrary set of api calls I would do something like this:

function getSearchData(){
    var deferred = $q.defer();
    var noOfCalls = apiList.length;
    var results = [];
    var called = 0;

    angular.forEach(apiList, function(item, key) {
        $http.get(url).then(function(result){
           results.push(result);
           called++;
           if(called == noOfCalls){
              deferred.resolve(results);
           }     
        })
   });

    return deferred.promise;
}

However if you know what each api call represents its better to use $.all in this way

function search1(){
      return $http.get(search1Url).then(function(result){
          // do something to it
          return result; 
      });
}

function search2(){
      return $http.get(search2Url).then(function(result){
          // do something to it
          return result; 
      });
}

function search3(){
      return $http.get(search3Url).then(function(result){
          // do something to it
          return result; 
      });
}

function search4(){
      return $http.get(search4Url).then(function(result){
          // do something to it
          return result; 
      });
}

function getSearchResult(){

    return $q.all([search1(), search2(), search3(), search4()]).then(function(results){
       // OPTIONAL  aggregate results before resolving
       return results;
    });
}
Share:
10,263
byrdr
Author by

byrdr

Web Developer/designer and letterpress printer.

Updated on June 26, 2022

Comments

  • byrdr
    byrdr about 2 years

    I'm working on an $http call that loops over each of multiple api's and returns all of the data in one object. I usually have the promise ready to resolve when the $http call has been made. Similar to this:

    function getAllData(api) {
        return $http({
            method: 'GET',
            url: '/api/' + api
        })
        .then(sendResponseData)
        .catch (sendGetVolunteerError);
    }
    

    The current function I have loops over each api and pushes each object in the api into an array and then pushes it into an overall array. I had this functioning, returning an multi-dimensional array, which needed to be flattened out.

    I'd like to return this in a promise, but am returning undefined. Here is what I have so far? Is there a better way to approach this?

    dataService:

    function getSearchData() {
        return {
            loadDataFromUrls: function () {
                var apiList = ["abo", "ser", "vol", "con", "giv", "blo", "par"];
                var deferred = $q.defer();
                var log = [];
                angular.forEach(apiList, function (item, key) {
                    var logNew = [];
                    $http({
                        method: 'GET',
                        url: '/api/' + item
                    }).then(function (response) {
                        angular.forEach(response.data, function (item, key) {
                            this.push(item);
                        }, logNew);
                        return logNew;
                    });
                    this.push(logNew);
                }, log);
                $q.all(log).then(
    
                function (results) {
                    deferred.resolve(
                    JSON.stringify(results))
                },
    
                function (errors) {
                    deferred.reject(errors);
                },
    
                function (updates) {
                    deferred.update(updates);
                });
                return deferred.promise;
            }
        };
    };
    

    Controller:

    function getSearchData(){
      return dataService.getSearchData.loadDataFromUrls;
    }  
    
    $scope.searchData = getSearchData();
    
  • GregL
    GregL over 9 years
    Did you mean $q.defer()?
  • JLRishe
    JLRishe over 9 years
  • byrdr
    byrdr over 9 years
    For some reason I'm returning undefined in the controller: function getSearchData(){ return dataService.getSearchData.loadDataFromUrls; } console.log(getSearchData());
  • JLRishe
    JLRishe over 9 years
    @byrdr That's because getSearchData returns an object with loadDataFromUrls as one of its properties, but you are accessing loadDataFromUrls as a property of getSearchData. Try: function getSearchData(){ return dataService.getSearchData().loadDataFromUrls; } console.log(getSearchData()); That does leave the question of why you have an extra layer between your data service and your getSearchData function.
  • byrdr
    byrdr over 9 years
    That makes sense, thanks. The above code currently logs the function itself.
  • byrdr
    byrdr over 9 years
    wrapping it in an iffe returns a promise Object {then: function, catch: function, finally: function}
  • JLRishe
    JLRishe over 9 years
    @byrdr Yeah, you would need another pair of parentheses to actually call it: console.log(getSearchData()());. But I think you're involving too many layers of functions. You can just do: var getSearchData = dataService.getSearchData().loadDataFromUrls; console.log(getSerachData());. That should log a promise.
  • JLRishe
    JLRishe over 9 years
    @byrdr Yes, once you have a promise object, you can call .then() on it to access the result. And there's no need to involve IIFEs here. You already have too many function layers, and adding more is not the answer. :)
  • Chris Noring
    Chris Noring over 9 years
    @JLRishe thanks for pointing out the anti pattern.. I learned something today.. cheers Chris
  • JLRishe
    JLRishe over 9 years
    @Chris Glad to lend a hand. You're still using it in getSearchResult, and what you need to pass to $q.all here is search1(), search2(), etc, not just search1, search2. And your other functions need return statements because they're not returning anything.
  • JLRishe
    JLRishe over 9 years
    You still have an extra return statement in getSearchResult, but here's an upvote. :)
  • byrdr
    byrdr over 9 years
    Everything is working correctly. thanks for your help.
  • Motoman
    Motoman almost 8 years
    I've been looking for something like this for some time now. Was aware of $q but not map. Works awesome in my factories. Thank You!