Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Node - Wait for loop to finish?

When the function below finishes and provides a finalized list of items in the array 'albums', I want it to call another function/do something else with the list.

Currently it posts [] before the function finishes and I know that's because of asynchronous execution, but I thought Node read linearly since it's single threaded?

function getAlbumsTotal(list, params){
    for(var i = 0; i<list.length; i++){
        api.getArtistAlbums(list[i], params).then(function(data) {
            for(var alb = 0; alb<data.body.items.length; alb++){
                albums.push(data.body.items[alb].id);
            }
        }, function(err) {
            console.error(err);
        });
    }
    console.log(albums);
    //do something with the finalized list of albums here
}
like image 701
Ralph Avatar asked Dec 18 '16 19:12

Ralph


People also ask

What is the difference between setImmediate vs nextTick?

nextTick() is used to schedule a callback function to be invoked in the next iteration of the Event Loop. setImmediate() method is used to execute a function right after the current event loop finishes. 2.

What is eventloop in NodeJS?

The event loop is what allows Node. js to perform non-blocking I/O operations — despite the fact that JavaScript is single-threaded — by offloading operations to the system kernel whenever possible. Since most modern kernels are multi-threaded, they can handle multiple operations executing in the background.

How node event loop works?

Event loop is an endless loop, which waits for tasks, executes them and then sleeps until it receives more tasks. The event loop executes tasks from the event queue only when the call stack is empty i.e. there is no ongoing task. The event loop allows us to use callbacks and promises.

How to wait for a function to finish in JavaScript?

To handle the asynchronous nature of JavaScript executions, you can use one of the three available methods to wait for a function to finish: This tutorial will help you learn all three methods, starting from using callback functions A callback function is a regular JavaScript function that you pass to another function.

How do you simulate wait time in async?

This simulated wait is accomplished by keeping a counter that only increases when the async function finishes. Doesn’t matter if it errors out or succeeds. When this counter matches the array length we know that we’re finally done with the transformations and we can quit.

What is simulated wait in JavaScript?

This simulated wait is accomplished by keeping a counter that only increases when the async function finishes. Doesn’t matter if it errors out or succeeds. When this counter matches the array length we know that we’re finally done with the transformations and we can quit. By quit, I mean return the transformed array back to the user.

Why can’t I just call res send after the loop ends?

Like mentioned earlier we can’t just call res.send after the loop ends because the transformations may not have finished. Instead in the above code we’re waiting until the last asynchronous function call finishes. This simulated wait is accomplished by keeping a counter that only increases when the async function finishes.


2 Answers

The callback function you provide to then is indeed executed asynchronously, which means it is only executed after the rest of the code in the current call stack has finished executing, including your final console.log.

Here is how you could do it:

function getAlbumsTotal(list, params){
    var promises = list.map(function (item) { // return array of promises
        // return the promise:
        return api.getArtistAlbums(item, params)
            .then(function(data) {
                for(var alb = 0; alb<data.body.items.length; alb++){
                    albums.push(data.body.items[alb].id);
                }
            }, function(err) {
                console.error(err);
            });
    });
    Promise.all(promises).then(function () {
        console.log(albums);
        //do something with the finalized list of albums here
    });
}

NB: Apparently albums is defined as a global variable. This is not so good a design. It would be better that each promise would provide its own subset of albums, and the Promise.all call would be used to concatenate those results into a local variable. Here is how that would look like:

function getAlbumsTotal(list, params){
    var promises = list.map(function (item) { // return array of promises
        // return the promise:
        return api.getArtistAlbums(item, params)
            .then(function(data) {
                // return the array of album IDs:
                return Array.from(data.body.items, function (alb) {
                    return alb.id;
                });
            }, function(err) {
                console.error(err);
            });
    });
    Promise.all(promises).then(function (albums) { // albums is 2D array
        albums = [].concat.apply([], albums); // flatten the array
        console.log(albums);
        //do something with the finalized list of albums here
    });
}
like image 130
trincot Avatar answered Sep 29 '22 23:09

trincot


If you want to use data returned from a loop in node.js you have to add a little extra code to check if you're on the last iteration of the loop. Basically you're writing your own "loop complete" check and running only when that condition is true.

I went ahead and wrote a complete, runnable example so you can break it down to see how it works. The important part is to add a counter, increment it after each loop, then check for when the counter is the same length of the list that you're iterating over.

function getArtistAlbums(artist, params){
  var artistAlbums = {
    'Aphex Twin':['Syro', 'Drukqs'],
    'Metallica':['Kill \'Em All', 'Reload']
  };
  return new Promise(function (fulfill, reject){
    fulfill(artistAlbums[artist]);
  });

}
function getAlbumsTotal(list, params){
  var listCount = 0;
  for(var i = 0; i<list.length; i++){
    getArtistAlbums(list[i], params)
      .then(function(data) {
        listCount++;
        for(var alb = 0; alb<data.length; alb++){
        //for(var alb = 0; alb<data.items.length; alb++){
          //albums.push(data.body.items[alb].id);
          albums.push(data[alb]);
        }
        // print out album list at the end of our loop
        if(listCount == list.length){
          console.log(albums);
        }

      }, function(err) {
        console.error(err);
      });
  }
  // prints out too early because of async nature of node.js
  //console.log(albums);
}

var listOfArtists = ['Aphex Twin', 'Metallica'];
var albums = [];

getAlbumsTotal(listOfArtists, 'dummy params');
like image 33
Jim Factor Avatar answered Sep 29 '22 21:09

Jim Factor