问题
I am attempting to make a file explorer using the 'fs' module of Node.js.
I wrote the following function which takes in a path and stores the contents of that path(files/folders) in an array and just sends it back using the callback.
listDir: function (path, myCallback) {
var resultObj = [];
fs.readdir(path, function (err, data) {
console.log('In listDir');
if (err) {
return myCallback(err, null);
} else {
data.forEach(function (value) {
fs.lstat(path + '/' + value, function (err, stats) {
if (err) {
console.log(err);
} else {
//console.log('Size-' + stats.isFile()+'\n');
if(stats.isFile()){
//console.log('is file----\n');
/*resultObj.push({
type: 'file',
name: value,
size: stats['size']
});*/
resultObj.push(value);
console.log(resultObj+'\n');
} else if(stats.isDirectory()){
//console.log('is folder----\n');
/*resultObj.push({
type: 'folder',
name: value,
size: stats['size']
});*/
resultObj.push(value);
console.log(resultObj+'\n');
}
}
});
});
console.log('Resultant obj-' + resultObj);
return myCallback(null, resultObj);
}
});
}
For each file/folder stored in 'data', i'm trying to check whether it is a file or a folder and depending on that i want to push an object into my 'resultObj' array. However, the return statement gets executed even before the forEach is complete and hence i get an empty resultObj everytime.
Why does that happen ? Why does it behave asynchronously even when i have provided a callback function ?
The output is something like this when i call the /test route with the path param--
In listDir
Resultant obj-
GET /test?path=F:\dummyFolder 304 15.210 ms - -
file1
file1,file2
file1,file2,folder3
Please help me with this, i'm really stuck with this assignment which has to be completed by tonight. And also it has been specifically mentioned in the assignment NOT TO USE ANY SYNCHRONOUS FUNCTION OF THE 'fs' MODULE. So how can i do this keeping that clause in mind ?
PS: Don't mind the comments, i used them for debugging.
回答1:
The problem is that you're not waiting for the fs.lstat()
function to finish for each file. You're queueing up all the fs.lstat()
calls but then not waiting for all of them to finish. You're just going to return back resultObj
when it is still []
.
You can fix this by adding a check inside the callback of your data.forEach()
to see how many of the items have had the callback for fs.lstat()
invoked.
listDir: (path, myCallback) => {
let resultObj = [];
fs.readdir(path, (err, data) => {
console.log('In listDir');
if (err) {
return myCallback(err);
}
let itemsCompleted = 0
data.forEach(value => {
fs.lstat(path + '/' + value, (err, stats) => {
itemsCompleted++
if (err) {
console.log(err);
} else {
if(stats.isFile() || stats.isDirectory()){
resultObj.push(value);
console.log(resultObj+'\n');
}
}
if(itemsCompleted >= data.length) {
return myCallback(null, resultObj)
}
});
});
}
However, the above approach is still more complicated than Promises with async/await
. Promises with async/await
is currently the preferred way to handle asynchronous control flow in Node. Utilizing util.promisify() to promisify fs.readdir()
and fs.lstat()
allows us to flatten the control flow and dramatically improve the readability of the listDir()
. Additionally, by using map()
instead of forEach()
we can use Promise.all()
to wrap the returned Promise from lstatAsync()
for each entry.
const {promisify} = require('util')
const fs = require('fs')
const {join} = require('path')
const readdirAsync = promisify(fs.readdir)
const lstatAsync = promisify(fs.lstat)
module.exports = {
listDir: async path => {
let resultObj = [];
let entries = await readdirAsync(path)
console.log('In listDir');
await Promise.all(entries.map(async value => {
try {
let stats = await lstatAsync(join(path, value))
if(stats.isFile() || stats.isDirectory()){
resultObj.push(value);
console.log(resultObj+'\n');
}
} catch (err) {
console.log(err)
}
}))
return resultObj
}
}
来源:https://stackoverflow.com/questions/48272330/function-behaving-asynchronously-even-after-a-callback-function-in-node-js