7

I'm writing an Angular.js service to pull some JSON feeds. Initially, the service does not know which/how many resources to request; they are dependent on ids returned by a another request.

I'm having a headache chaining the $http service requests together. Is there a commonly used pattern to do this?

I've tried the Array.reduce technique suggested on another thread, but had problems syncing the ids and the requested data.

This is what I have so far. Does anyone have any suggestions?

aService.factory('dummy', function($http){
    // Dummy resources.
    var resources = [   
      'http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js',
      'http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js',
      'http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js'
    ];
    return {
        data: function(callback){

            var jquerySources = []

            var promiseChain = $http({method: 'GET', url: resources[0]});
            var l = resources.length
            for (var i = 1; i < l; i++ ){
                promiseChain.then(function(data){

                    jquerySources.push({
                        url: resources[i],
                        source: data
                    });

                    promise_chain = $http({method: 'GET', url: resources[i]});
                    if (i === l){
                        return callback(jquerySources);
                    }
               });
            }
        }
    }
});

Thankyou.

Pedro Montoto García
  • 1,672
  • 2
  • 18
  • 38

2 Answers2

7

If you would need to do requests sequentially, you would create a promise which you could use as the head of your chain. Then, you could chain $http calls up to that head and resolve the head promise:

aService.factory('seq', function($http, $q){
    // Dummy resources.
    var resources = [   
      'http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js',
      'http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js',
      'http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js'
    ];
    var deferred = $q.defer();
    var resourcePromise = deferred.promise;
    var res = [];
    angular.forEach(resources, function(resource){
      return resourcePromise.then(function(){
        return $http({ method: 'GET', url: resource });
      }).then(function(data){
        res.push({res: resource, data : data});
      });
    });

    deferred.resolve();

    return {
        getResource: resourcePromise.then(function(){
          return res;
        })
    };
});

but if requests would be in parallel - then it would be simplier solution. Just array of promises and simply call $q.all function for waiting to resolve all promises.

aService.factory('par', function($http, $q){
    // Dummy resources.
    var resources = [   
      'http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js',
      'http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js',
      'http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js'
    ];
    var promises = [];
    var res = [];
    angular.forEach(resources, function(resource){
      promises.push(
        $http({ method: 'GET', url: resource }).then(
          function(data){
            res.push({res: resource, data : data});
          })
        );
    });

    return {
        getResource: $q.all(promises).then(function(){
          return res;
        })
    };
});

Also note that in both cases we have res array for collecting results of requests.

EDIT: Plunker with example

Stewart
  • 1,659
  • 4
  • 23
  • 35
Oleksii Aza
  • 5,368
  • 28
  • 35
  • I was just searching for something similar, and this is awesome Oleksii. A bit of a peasant question, but I was then trying to get that data into a controller and having trouble. I can console.log the data out in the $http function in the factory, but not able to get the output in json into a scope var in a controller. Do you have any suggestions on how to best do this? – Daniel F May 21 '14 at 17:54
  • Yes, bassically you need to use .then() function after calling a service. I've just added plunker with example to my answer, take a look on it. – Oleksii Aza May 21 '14 at 18:57
  • brilliant, thanks so much Oleksii, I really appreciate it. I just upvoted your answer as well. Thanks for the help. – Daniel F May 21 '14 at 20:06
  • Thanks for the answer! If a final step is required after the chaining promises, you can put an additional resourcePromise.then(()=>{ finalWork();}) after. – Lin Song Yang May 27 '16 at 05:48
  • I don't think your sequential example is actually sequential. You would need to re-assign the promise or use reduce(). – graywh Dec 08 '16 at 21:25
3

You can indeed do this with reduce:

var chain = resources.reduce(function (sourcesPromise, url) {
    return sourcesPromise.then(function (sources) {
        return $http({method: 'GET', url: url})
        .then(function (data) {
            sources.push({url: url, source: data});

            return sources;
        });
    });
}, $q.when([]));

chain.then(function (sources) {
    // [{url, source}, ...]
});

based on How to chain a variable number of promises in Q, in order?

Community
  • 1
  • 1
Stuart K
  • 3,212
  • 4
  • 22
  • 27
  • Here's an excellent article on using reduce for promise chaining: https://css-tricks.com/why-using-reduce-to-sequentially-resolve-promises-works/ – Evan Nov 21 '19 at 17:56