Jonathan Jonathan - 6 months ago 10
Node.js Question

Array returning Undefined because of asynchrony

I am accessing the API Trello, but I came across the following problem:

Trello access the information, getting the id of each existing row, the code is as follows:

var x;
var numberCardsByList = [];

trello.get("/1/boards/[idBoard]/lists/all", function(err, data) {
if (err) throw err;
console.log("Number of list: " + data.length);

for(var i=0; i<data.length; i++){
x = data[i];
findNumberCards(x);
}
});


As you can see, after getting the size, I walk all these queues with is, within the loop, attach each row in a variable x and call a function that aims to get the number of cards that queue. The code for the number of cards is as follows:

function findNumberCards(x){
trello.get("/1/lists/"+x.id+"/cards", function(err, dados){
if(err) throw err;
console.log("Name List: " + x.name + " have " + dados.length + " cards");
numberCardsByList[x.name] = dados.length;
});
}


Until then all right, but when I try to access the vector
numberCardsByList
after the end of the search in Trello, it returns undefined:

var x;
var numberCardsByList = [];

trello.get("/1/boards/[idBoard]/lists/all", function(err, data) {
if (err) throw err;
console.log("Quantidade de Filas: " + data.length);

for(var i=0; i<data.length; i++){
x = data[i];
findNumberCards(x);
}
});
console.log(numberCardsByList);


I am aware that it is because of asynchrony, however, can not solve.

Answer

The problem you're facing has been solved many times before. If you want to know more, search for the keyword "Promise". If you're familiar with jQuery, try and look up: $.whenAll, $.ajax().done, $.ajax().always, etc.

If you want to come up with a light weight solution yourself, here's a pointer:

By the time you get to your console.log(numberCardsByList), your requests triggered by findNumberCards haven't yet completed, making the Array empty. You'll need to make sure you know when all findNumberCards requests have completed and then log them. Alternatively, you could log them every time one of them completes.

There are roughly two approaches:

  1. Keep track of your open requests and call a function when a request is handled.
  2. Observe your numberCardsByList object and call a function whenever items are added (you won't know if they were added async or synchronously)

I'd suggest going with the first approach. Check out this example code and the comments:

var numberCardsByList = {};

// This array will store the url for every open request
var openRequests = [];

var removeRequest = function(url) {
  var index = openRequests.indexOf(url);
  if (index === -1) return;

  // Remove url from array
  openRequests = openRequests
    .slice(0, index)
    .concat(openRequests
      .slice(index + 1));
};

// This will be called whenever one request completes
var onComplete = function(url) {
  removeRequest(url);
  
  // When all have completed, we can call our callback
  if (openRequests.length === 0) {
    onAllComplete();
  }

});

// This will be called when there are no open requests left
var onAllComplete = function(data) {
  console.log(numberCardsByList);
}


trello.get("/1/boards/[idBoard]/lists/all", function(err, data) {
  if (err) throw err;
  console.log("Number of list: " + data.length);

  for (var i = 0; i < data.length; i++) {
    x = data[i];
    findNumberCards(x);
  }
});


function findNumberCards(x) {
  var url = "/1/lists/" + x.id + "/cards";
  
  // Before we make the request, we register it:
  openRequests.push(url);

  trello.get(url, function(err, dados) {
    numberCardsByList[x.name] = dados.length;
    
    // When it is completed, we call onComplete
    onComplete(url);
  });
};

Note that this onAllComplete isn't 100% safe: it might be called multiple times if a request finishes before the next one is started.

Concluding:

If you can, I'd use a library to handle promises. If you want to try and build something yourself, you could try and keep track of the requests and execute a callback when they've all completed.