0

I can't seem to figure this out.

First, I call an API with a list of radios, and I check how many radios exists in the API.

Then, I call another API with a 'for loop' based on the length of the radio.length, and push the data into an array.

Here is my code using (Angular JS)

var radioShows = [];

$http.get('http://api.example.com/radios/').success(function(results) {

        var totalRadioLength = [];

        for (i = 0; i <= results.length; i++) {

            totalRadioLength.push(i);
            console.log(totalRadioLength) // Outputs 7

            $http.get('http://api.example.com/radioshows/fr/radio/' + results[i].id + '/?day=' + x + '').success(function(resultShows) {

                if (resultShows.success === false) {
                    // console.log('No')
                } else {
                    // console.log('Yes')
                    radioShows.push(resultShows);

                }
            })
        }
 })

This seems to work fine, except my array radioShows comes out in a random order each time. What is the best way to have the array output based on the order of the first API call?

Kenneth Salomon
  • 1,352
  • 11
  • 18
anon
  • 594
  • 6
  • 25
  • What is your html code? You can order there or order the elements when your radios are retrieved by the server in your `success` callback. – Mindastic Jul 15 '15 at 20:11
  • 1
    I'm not exactly familiar with Angular, but I know in JQuery, you can set a parameter for an AJAX query to be synchronous instead of asynchronous. If there's something like that in Angular, that would solve your problem – Osuwariboy Jul 15 '15 at 20:12
  • The $http service in Angular is hard-coded to be asynchronous, if I recall correctly. – Onite Jul 15 '15 at 20:12
  • 2
    The problem is you're making 7 requests simultaneously. Therefore whichever response arrives first gets pushed to `radioShows` first. If you want to control the sequence of requests use something like async.js – slebetman Jul 15 '15 at 20:13

4 Answers4

2

The reason that you get the results in an unpredictable order is that you put them in the array in the order that the responses arrive, and there is no guarantee that the request finish in the order that you send them. Put the results in the array according to the index of the input data. You can use a function to create a scope where each iteration gets its own variable i:

for (i = 0; i <= results.length; i++) {

  (function(i){

    totalRadioLength.push(i);
    console.log(totalRadioLength) // Outputs 7

    $http.get('http://api.example.com/radioshows/fr/radio/' + results[i].id + '/?day=' + x + '').success(function(resultShows) {

      if (resultShows.success === false) {
        // console.log('No')
      } else {
        // console.log('Yes')
        radioShows[i] = resultShows;
      }

    });

  })(i);

}
Guffa
  • 687,336
  • 108
  • 737
  • 1,005
1

As it has been mentioned your http requests are all kicked off simultaneously and the resolve at different times because of that. The similest solution would be to make the http requests synchronously, however that is unrealistic but there is a way to wait for all of the async calls to complete then process them.

Promises to the rescue! Using promises you can kick off all of your http requests but wait for them to complete before processing them. This will change the output of the code to be a promise though so what ever is calling this will need to be changed to use the returned promise.

var getRadioShows = function() {
    // Create a promise that will resolve when all results have been compiled.
    var deferredResults = $q.defer();

    // Create an array that will store all of the promises
    //   for the async http requests
    $http.get('http://api.example.com/radios/').success(function(results) {

        var deferredHttpCalls = [];
        for (i = 0; i <= results.length; i++) {
            var deferredRequest = $q.defer();
            $http.get('http://api.example.com/radioshows/fr/radio/' + results[i].id + '/?day=' + x + '').success(function(resultShows) {
                if (resultShows.success === false) {
                    // I am guessing this is not a failure case.
                    // If it is then you can call deferredRequest.reject('Request was not successful');

                    deferredRequest.resolve();
                } else {
                    deferredRequest.resolve(resultShows);
                }
            }).error(function() {
                // reject this request and will also cause
                //   deferredResults to be rejected.
                deferredRequest.reject(err);
            });

            // Gather all of the requests.
            deferredHttpCalls.push(deferredRequest);
        }

        // Wait for all of the promises to be resolved.
        $q.all(deferredHttpCalls).then(function(results) {
            // 'results' is an array of all of the values returned from .resolve()
            // The values are in the same order and the deferredHttpCalled array.

            // resolve the primary promise with the array of results.
            deferredResults.resolve(results);
        });
    }).error(function(err) {
        // reject the promise for all of the results.
        deferredResults.reject(err);
    });

    return deferredResults;
}

Then you would used the call that returns the promise like so.

getRadioShows().then(
    // success function, results from .resolve
    function(results) {
        // process results
    },
    // error function, results from .reject
    function(err) {
        // handle error
    }
)

For more information regarding how $q, and all Promise/A+ libraries work, see the Promise/A+ standard here: https://promisesaplus.com/

Enzey
  • 5,254
  • 1
  • 18
  • 20
0

It looks like you are calling the $http.get each loop through. I would recommend storing the results using the i variable instead of push.

The reason for this is because you are expecting the time for each request to take the same time and push to the array in the proper order, but in real world, it is not. That is why the order appears random, because it is only able to push to the array when the result comes back, but it already is proceeding to the next step while it waits.

Kenneth Salomon
  • 1,352
  • 11
  • 18
0

That's happening because the requests to get the radio shows run in async mode. Maybe you can use promises to run the requests synchronously, please check this link:

how to make synchronous http request in angular js

Also you can save the radio id for each radio show and then re-order the resulting array by that id.

radioShows.push({ id: results[i].id, shows: resultShows});
Community
  • 1
  • 1
Diego Garcia
  • 369
  • 2
  • 4
  • I am curios in which way ever promises can help to make something synchronous when promises are for handling _asynchronous_ operations?! In the mentioned SO question the accepted answer gives the async approach instead of blocking (synchrounous). It just fixes the code but in no way makes the http request synchronous – Kirill Slatin Jul 15 '15 at 21:59