Katie
Katie

Reputation: 48308

Asynchronous for loop: how do I make the for loop wait for function to finish?

I'm racking my brain trying to figure out how to sequence this / place callbacks to get what I need.

I have a loop that checks for the existence of files. I'd like it to callback when it's done, but the for loop and the callback finish before the "fs.open" finishes... typical asynchronous problem.

I am using node v0.10.29, express v3.14.0, and am looking at using the "async" library, but again, just can't figure out the logic that I need...

Here is what I have:

Input

function checkForAllFiles(callback)
{
    var requiredFiles = [];
    requiredFiles[requiredFiles.length] = "../Client/database/one.js";
    requiredFiles[requiredFiles.length] = "../Client/database/two.dat";
    requiredFiles[requiredFiles.length] = "../Client/database/three.xml";
    requiredFiles[requiredFiles.length] = "../Client/database/four.dat";
    requiredFiles[requiredFiles.length] = "../Client/database/five.dat";
    requiredFiles[requiredFiles.length] = "../Client/database/six.xml";

    var missingFiles = [];
    for(var r=0; r<requiredFiles.length; r++)
    {
        fs.open(requiredFiles[r], 'r', function(err, fd){
            if(err)
            {
                missingFiles[missingFiles.length] = err.path;
                console.log("found missing file = ", err.path);
            }
        });
        console.log("r = ", r);
    }
    console.log("sending callback: ", missingFiles);
    callback(missingFiles);
}

Output

0
1
2
3
4
5
sending callback: []
found missing file: ../Client/database/three.xml

Desired Output

0
1
found missing file: ../Client/database/three.xml
2
3
4
5
sending callback: ["../Client/database/three.xml"]

Upvotes: 0

Views: 197

Answers (2)

Mike S
Mike S

Reputation: 42355

I would use the reject method in the async module (which I see you've already found). What it will do is return an array in its callback that contains any elements that don't match a specified check function. For the check function, I'd recommend just using fs.exists instead of watching for an error on fs.open.

Using those functions you can actually reduce the whole check to one line. Something like this:

function checkForAllFiles(callback)
{
    var requiredFiles = [];
    requiredFiles[requiredFiles.length] = "../Client/database/one.js";
    requiredFiles[requiredFiles.length] = "../Client/database/two.dat";
    requiredFiles[requiredFiles.length] = "../Client/database/three.xml";
    requiredFiles[requiredFiles.length] = "../Client/database/four.dat";
    requiredFiles[requiredFiles.length] = "../Client/database/five.dat";
    requiredFiles[requiredFiles.length] = "../Client/database/six.xml";

    async.reject(requiredFiles, fs.exists, callback);
}

callback will get called with an array that contains just the files that don't exist.

Upvotes: 3

piergiaj
piergiaj

Reputation: 629

Use the async library and the eachSeries method. Example:

async.eachSeries(array,
     function(element, next) {
          // do something with element
          next();
     }
);

It will sequentially go through the array and process each element. Calling next goes to the next element. Series makes sure it does it in the order of the array, otherwise the order of going through the array is not guaranteed. If you have other async functions called within it, just pass the next function around and call it when done with all the needed functions and the next array element will be processed.

Maybe something like this:

var missingFiles = []
async.eachSeries(requiredFiles, function(file, nextFile){
 fs.open(file, 'r', function(err, fd){
        if(err)
        {
            missingFiles[missingFiles.length] = err.path;
            console.log("found missing file = ", err.path);
        }
       nextFile();
    });
    console.log("r = ", file);
});
console.log("sending callback: ", missingFiles);
callback(missingFiles);

Upvotes: 1

Related Questions