How to do for loop in javascript without blocking

Problem

I have folowing script

var email_list = ['email1@email.com', 'email2@email.com',....'email100@email.com'];
for(i=0;i<email_list.length;i++){
  if(checkEmail(email_list[i])){
    //do processing save in db and email to email addresses.
  }
}

This code will be blocking in nodejs how to make this non blocking?

Problem courtesy of: Yalamber

Solution

You can do this without blocking the event loop at all, by using a recursive loop. This way what you end up with is only launching one database worker per call, at a give time. Assuming the database work you were doing was asynchronous, your code didn't really block the event loop. But the foor loop still launched a bunch of workers simultaneously, which will tend to clog the event loop(not block it). And you are right in that it is blocking the event loop while your for loop is counting from 0, to whatever the size of your array is. The following does exactly the same thing, but you only launch one database worker at a time(good), and you never count from 0 to length. Each worker is popped off the list after the work on the current email is done, and your global event loop is left to process other things, not email_list.length database requests simultaneously.

var email_list = ['email1@email.com', 'email2@email.com', 'email100@email.com'];


function checkEmailList(emails, emailCallBack, completionCallback) {

    var someDataCollectdOverAllEmails = '';

    function checkEmailAsync(email) {
        db.doSomeDBWorkAsync(email, function (data) {

            someDataCollectdOverAllEmails += data;

            if (email_list.length) {
                checkEmail(email_list.pop()); //If there are still emails to be checked, check the next one ine line
            } else {
                completionCallback(someDataCollectdOverAllEmails);//IF not, call the completionCallBack
            }

            emailCallBack(data);
        });
    }

    checkEmailAsync(emails.pop());
}

function logIndividualEmailData(data) {
    console.log('Sningle Email: ' + data);
}

function logGlobalEmailData(data) {
    console.log('All Email Data: ' + data);
}

checkEmailList(email_list, logIndividualEmailData, logGlobalEmailData);

Process.nextTick example

process.nextTick(function () {
    'use strict';
    console.log('printed second');
    while (true); 
});

process.nextTick(function () {
    'use strict';
    console.log('never printed');
});

console.log('printed first');

Note however that in the example below, despite the fact that loopForever will run forever, it still allows both of our files to be read out. If we just had while(true) it would of course block and not allow this and one of our files data would not be printed out.

var files = ['blah.js', 'file.js'];
for(var i = 0; i < files.length; i++) {
    fs.readFile(files[i], function (err, data) {
        console.log('File data' + data);

        function loopForver(loop) {//asynchronously loop forever, pretty cool, but only useful for really specific situations!
            process.nextTick(function () {
                if(loop) {
                    console.log('looping');
                    loopForver(true);
                }
            });
        }
        loopForver(true);
    });
}
Solution courtesy of: ChrisCM

Discussion

If I need to do stuff after the emails all send, I use the async library (docs), which provides some useful functions for control flow.

You will still need to rewrite checkEmail(email) into checkEmail(email, callback) as @S.D. suggests. In checkEmail you will want to call callback after everything is completed. This probably means that you will nest callbacks, calling the second async thing (sending the email) only after the first (db query) has completed successfully. I also suggest that you follow convention by using the first callback argument as an err parameter. If you callback(null) you are explicitly saying 'there was no error'. @S.D.'s solution suggests instead callback(ok) which is the opposite of convention.

Here is an example showing a couple nested asynchronous functions and the async library.

edit - use async.eachLimit instead of async.each so you don't execute all 100 calls simultaneously

(function main(){
  var emails = ["a@b", "c@d"];
  var async = require('async');
  async.eachLimit(
    emails           // array to iterate across
   ,10               // max simultaneous iterations
   ,checkEmail       // an asynchronous iterator function
   ,function(err){   // executed on any error or every item successful
      console.log('Callback of async.eachLimit');
      if(err){ 
        console.log('Error: '+err) 
      } else {
        console.log('All emails succeeded');
      };  
    }   
  );  
  console.log('Code below the async.eachLimit call will continue executing after starting the asynchronous jobs');
})();

function checkEmail(email, callback){
  fetchFromDb(email, function(err, obj){
    if(err){ return callback(err) };
    sendEmail(email, function(err, obj){
      if(err){ return callback(err)};
      console.log('Both fetchFromDb and sendEmail have completed successfully for '+email);
      callback(null);
    }); 
  }); 
};

function fetchFromDb(email, callback){
  process.nextTick(function(){ // placeholder, insert real async function here
    callback(null);
  }); 
};

function checkEmail(email, callback){
  process.nextTick(function(){ // placeholder, insert real async function here
    callback(null);
  }); 
};
Discussion courtesy of: Plato

This recipe can be found in it's original form on Stack Over Flow.