3
var result = { controllers: [], views: [], models: [] };
var dirs = ['controllers', 'views', 'models'];

dirs.forEach(function(dirname) {
    fs.readdir('./' + dirname, function(err, res) {
        if (err) throw err;
        result[dirname] = res;
        // #2
    });
});

// #1

In this snippet of code, having console.log(result); running at #1 (see above), empty controller, views, and models arrays just as initialized will be logged. However, I need the loop to fill the arrays with corresponding file names read via fs.

console.log(result); at #2 will log the result object filled with the desired values after the third iteration.

I believe this has something to do with the asynchronous nature of Node.js / JavaScript callbacks. Please forgive me if I'm not understanding how JavaScript variable scopes and async methods work, I'm all new to this.

0

5 Answers 5

2

Do it this way:

var result = { controllers: [], views: [], models: [] };
var dirs = ['controllers', 'views', 'models'];
var pending = 0;

dirs.forEach(function(dirname) {
    pending++;
    fs.readdir('./' + dirname, function(err, res) {
        pending--;
        if (err) throw err;
        result[dirname] = res;
        if (pending===0) goOn();
    });
});
function goOn() {
    // #1
}
Sign up to request clarification or add additional context in comments.

2 Comments

Looks good so far. I'm not fully convinced though since this solution incorporates nesting – still, this is a working and reasonable solution. Cheers!
@thejh what is the guarantee that pending has right value inside callback?
2

I believe this has something to do with the asynchronous nature of Node.js / JavaScript callbacks.

Yes, this is probably the reason why, when you try to output the content of your result variable at #1, it's empty. At the time of running at #1 data are simply not yet fetched because "fetching" action happens druing the execution of readdir's callback at #2. I would recommend to look at some of the resources about asynchronous paradigms stated in this answer in order to get the bigger/better picture of how callbacks and asynchronous programming works.

2 Comments

Be careful. Not all callbacks will always execute asynchronously.
Thanks, I read all of the "Control Flow in Node.js" series. Nice resource!
0

For people confused about wording, here is the output of an example with a '1' file in each of the directories:

~/Documents/$ node test.js 
{ controllers: [], views: [], models: [] } 1
{ controllers: [], views: [ '1' ], models: [] } 2
{ controllers: [ '1' ], views: [ '1' ], models: [] } 2
{ controllers: [ '1' ],
  views: [ '1' ],
  models: [ '1' ] } 2

This is because going into the file system requires async operations to occur. So since Node does not block the values afterwards will be the same as before the async operations are done.

1 Comment

This is totally reasonable, but using fs.readdirSync() doesn't work either.
0

It does have to do with callbacks. In

fs.readdir('./' + dirname, function(err, res) {
    if (err) throw err;
    result[dirname] = res;
    // #2
});

the function passed in is a callback, that executes when the directory is fully read. Since it is asynchronous, fs.readdir() returns before the function actually executes.

So basically, after your forEach finishes, you have 3 functions waiting to be executed as callbacks (one for each directory). The code does not wait for the callbacks to happen before continuing to execute, so if #1 is reached before the directories are read, it will log your result object before the callbacks execute and modify it appropriately.

You could use fs.readdirSync instead, but ONLY IF it would not be bad for your application if this code blocked briefly / there is no danger of this code blocking indefinitely and stalling your program. If you need it to remain asynchronous, look at thejh's answer.

1 Comment

Not +1ing because you really should warn about fs.readdirSync being a bad idea in many cases.
-1

I beleive it is to do with that the value if dirname changes after the return of the first function. To solve, simply copy the dirname into the scope for the second function call;

dirs.forEach(function(dirn) {
    var dirname = dirn;  // make a copy here
    fs.readdir('./' + dirname, function(err, res) {
        if (err) throw err;
        result[dirname] = res;
        // #2
    });
});

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.