7

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();
Thor Jacobsen
  • 8,621
  • 2
  • 27
  • 26
byrdr
  • 5,197
  • 12
  • 48
  • 78
  • 1
    I'm not 100% sure what's happening here - are you not pushing empty arrays (`logNew`) to the `log` array, which you want to wait for? Should you not be pushing the `promise` returned by `$http` instead? – Thor Jacobsen Mar 20 '15 at 14:12

3 Answers3

17

$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;        
            });
        }
    };
}
JLRishe
  • 99,490
  • 19
  • 131
  • 169
  • For some reason I'm returning undefined in the controller: function getSearchData(){ return dataService.getSearchData.loadDataFromUrls; } console.log(getSearchData()); – byrdr Mar 20 '15 at 14:27
  • @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. – JLRishe Mar 20 '15 at 14:29
  • That makes sense, thanks. The above code currently logs the function itself. – byrdr Mar 20 '15 at 14:33
  • wrapping it in an iffe returns a promise Object {then: function, catch: function, finally: function} – byrdr Mar 20 '15 at 14:34
  • @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 Mar 20 '15 at 14:34
  • @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. :) – JLRishe Mar 20 '15 at 14:35
  • Everything is working correctly. thanks for your help. – byrdr Mar 20 '15 at 15:16
  • 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! – Jessica Aug 05 '16 at 00:46
3

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;
    });
}
Chris Noring
  • 471
  • 3
  • 9
  • Did you mean `$q.defer()`? – GregL Mar 20 '15 at 14:22
  • [What is the deferred antipattern and how do I avoid it?](http://stackoverflow.com/questions/23803743/what-is-the-deferred-antipattern-and-how-do-i-avoid-it) – JLRishe Mar 20 '15 at 14:27
  • @JLRishe thanks for pointing out the anti pattern.. I learned something today.. cheers Chris – Chris Noring Mar 20 '15 at 14:41
  • @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 Mar 20 '15 at 14:43
  • You still have an extra return statement in `getSearchResult`, but here's an upvote. :) – JLRishe Mar 20 '15 at 14:51
0

You should add a list of promises in $q ( not resolved promises like in your code ) , which is a $promise service

Example:

var firstPromise = service1.getMethod1().$promise;
var secondPromise = service2.getMethod2().$promise;
$q.all([firstPromise, secondPromise]).then(function(dataList){
     // dataList[0] will be result of `firstPromise`
     // dataList[1] will be result of `secondPromise`
});
Cosmin
  • 2,184
  • 21
  • 38