Reputation: 5070
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.
Upvotes: 3
Views: 3267
Reputation: 2699
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.
Upvotes: 0
Reputation: 68453
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.
Upvotes: 2
Reputation: 45578
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
}
Upvotes: 2
Reputation: 246
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.
Upvotes: 0
Reputation: 14718
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
});
});
Upvotes: -1