1

I was trying to make multiple API requests and store responses into the array.

Array stayed empty because I didn't know about async behavior of for loop.

const results = [];
          
for (let i = 0; i < apiUrls.length; i++) {    
  apiCall(apiUrls[i], res => {
    results.push(res);
  });
} 

console.log(results) // [] 

So I rewrite it to:

const results = []

async function makeApiCalls () {
  for (const apiUrl in apiUrls) {
    const res = await apiCall(apiUrl)
    results.push(res)
  }
  
  console.log(results) // [data, someData, otherData...] 
}

makeApiCalls()

It works! But runs in sequence. We can improve it to run in parallel like this:

let results = []

async function makeApiCalls () {
  const promises = []

  // fire all requests
  for (const apiUrl in apiUrls) {
    promises.push(apiCall(apiUrl))
  }

  // wait until all promises are resolved and copy responses to array
  results = [...await Promise.all(promises)];

  console.log(results) // [data, someData, otherData...] 
}

makeApiCalls()
  • 1
    are you using any library to make ajax call? can you provide the fiddle? – gurvinder372 Jan 04 '16 at 13:43
  • 1
    Are you attempting to inspect the value of `stats` immediately after this perhaps? [This question](http://stackoverflow.com/questions/23667086/why-is-my-variable-unaltered-after-i-modify-it-inside-of-a-function-asynchron) and/or [this question](http://stackoverflow.com/questions/14220321/how-to-return-the-response-from-an-asynchronous-call) are worth a read if so. – James Thorpe Jan 04 '16 at 13:44
  • "it seems that array ist not visible in function" — What output do you get that leads you to that conclusion? – Quentin Jan 04 '16 at 13:45
  • in console i get 'pushed OK' 2 times, but array is empty – Vladimir Gerasimenko Jan 04 '16 at 13:46
  • 1
    At what point are you determining the array is empty? Can you show that code too? – James Thorpe Jan 04 '16 at 13:50
  • 1
    if the array were not visible in the function, then the `push` would fail and the program would stop before it got to the `console.log`. –  Jan 04 '16 at 13:57

3 Answers3

1

Looping over asynchronous calls is not going to work very well, because you'll have no idea when you're finished and the stats array is populated. You need some additional machinery to handle this. Without using async or promises, the basic idea is:

var stats = [];
var finishedCount = 0;  // keep track of how many callbacks have completed

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

    http.get(tempBackends[i], function(res) {

        console.log("Received response: " + res.statusCode);

        if(res.statusCode == 200) {
            stats.push('OK');
            console.log('pushed OK\n');
        }
        else {
            stats.push('Not OK');
            console.log('pushed Not OK\n');
        }

        // ADD LOGIC HERE TO HANDLE ALL CALLBACKS HAVING FINISHED
        finishedCount++;
        if (finishedCount === tempBackends.length) {
          console.log("ALL DONE!! stats is", stats);
        }

    });
} 

However, with this approach, the stats array is not going to be aligned with tempBackends. The order of the values in stats will be based on the order in which the asynchronous calls finished. In addition, this style of code is going to be hard to maintain. Instead, you should use async or promises. The async approach would be:

async.map(
  tempBackends, 
  function(backend, callback) {
    http.get(backend, function(res) {
      callback(null, res.statusCode === 200 ? "OK" : "Not OK");
    });
  function(err, data) {
    console.log("ALL DONE!! stats is", data);
  }
);

The promise approach is more readable and writable. First, make a promisified version of http.get:

function getPromise(backend) {
  return new Promise(function(resolve) {
    http.get(backend, function(res) {
      resolve(res.statusCode === 200 ? "OK" : "Not OK");
    });
  });
}

Now you can just write

Promise.all(tempBackends . map(getPromise)) .
  then(function(stats) {
    console.log("ALL DONE!! stats is", stats);
  });
0
var http = require('http');

var list = ['http://rest-service.guides.spring.io/greeting',
            'http://rest-service.guides.spring.io/greeting',
            'http://rest-service.guides.spring.io/greeting',
            'http://rest-service.guides.spring.io/greeting',
            'http://rest-service.guides.spring.io/greeting'];

var responseArray = [];

var calls = function(){
  var index = 0;
  var callWithIndex = function (i){
    http.get(list[i], function(apiResponse) {
      var statusCode = apiResponse.statusCode;

      apiResponse.on('data', function(chunk) {

      });

      apiResponse.on('end', function() {
        responseArray.push(statusCode);

        i += 1;

        if(i < list.length){
          callWithIndex(i);
        } else {
          console.log(responseArray.toString());
        }

      });

    });
  };
  callWithIndex(index);
};

calls();

If you mess with nodejs, you should know which callback you should execute your next iteration and when you should process your data. Notice how I called the next iteration in the response.on('end') part as well as how I pushed the statusCode to the array there as well.

ardilgulez
  • 1,856
  • 18
  • 19
-2

Try passing your array variable stats inside the function, because the scope of a function is local.

http.get(tempBackends[i], function(res,stats) { // access stats here now }

Adeel Raza
  • 628
  • 5
  • 17
  • 2
    This will not work at all, because `http.get` has no idea it needs to pass this array to the callback. –  Jan 04 '16 at 13:56