Angular $q returning promise multiple $http calls
Asked Answered
D

3

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();
Delarosa answered 20/3, 2015 at 14:0 Comment(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?Licorice
P
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;        
            });
        }
    };
}
Panchromatic answered 20/3, 2015 at 14:21 Comment(8)
For some reason I'm returning undefined in the controller: function getSearchData(){ return dataService.getSearchData.loadDataFromUrls; } console.log(getSearchData());Delarosa
@Delarosa 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.Panchromatic
That makes sense, thanks. The above code currently logs the function itself.Delarosa
wrapping it in an iffe returns a promise Object {then: function, catch: function, finally: function}Delarosa
@Delarosa 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.Panchromatic
@Delarosa 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. :)Panchromatic
Everything is working correctly. thanks for your help.Delarosa
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!Protecting
B
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;
    });
}
Blackbeard answered 20/3, 2015 at 14:17 Comment(5)
Did you mean $q.defer()?Mize
What is the deferred antipattern and how do I avoid it?Panchromatic
@Panchromatic thanks for pointing out the anti pattern.. I learned something today.. cheers ChrisBlackbeard
@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.Panchromatic
You still have an extra return statement in getSearchResult, but here's an upvote. :)Panchromatic
I
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`
});
Illbred answered 20/3, 2015 at 14:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.