It see at least three errors:
When you hit this if
statement if(! /^\..*/.test(file)){
and it does not execute the if
block, then the parent promise is never settled.
There is no error handling on fs.stat()
so if you get an error on that call, you are ignoring that and will be attempting to use a bad value.
The error handling on your call to fs.readdir()
is incomplete and will leave you with a promise that is never settled (when it should be rejected).
For a robust solution, you really don't want to be mixing promises and callbacks in the same code. It leads to the opportunity for lots of mistakes, particularly with error handling (as you can see you had at least three errors - two of which were in error handling).
If you're going to use Promises, then promisify the async operations you are using at the lowest level and use only promises to control your async code flow. The simplest way I know of to promisify the relevant fs
operations is to use the Bluebird promise library with its Promise.promisifyAll()
. You don't have to use that library. You could instead manually write promise wrappers for the async operations you're using.
Here's a version of your code using the Bluebird promise library:
const Promise = require('bluebird');
const fs = Promise.promisifyAll(require('fs'));
function filterFiles() {
return fs.readdirAsync(rootDir).then(function(files) {
let fileArray = [];
let dirArray = [];
// filter out entries that start with .
files = files.filter(function(f) {
return !f.startsWith(".");
});
return Promise.map(files, function(f) {
return fs.statAsync(f).then(function(stats) {
if (stats.isDirectory()) {
dirArray.push(f);
} else {
fileArray.push(f);
}
});
}).then(function() {
// make the resolved value be an object with two properties containing the arrays
return {dirArray, fileArray};
});
});
}
filterFiles().then(function(data) {
res.json(data);
}).catch(function(err) {
// put whatever is appropriate here
res.status(500).end();
});
This was rewritten/restructured with these changes:
- Use promises for all async operations
- Fix all error handling to reject the returned promise
- Filter out files starting with a
.
synchronously before processing any files (simplifies async processing).
- Use
Promise.map()
to process an array of values in parallel.
- In the
filterFiles().then()
handler, handle errors
- You can't
res.send()
a Javascript object so I used res.json(data)
instead (though I'm not sure what exactly you really want to send).
- Replace regex comparison with more efficient and simpler to understand
.startsWith()
.
If you don't want to use the Bluebird promise library, you can make your own promise wrappers for the fs methods you use like this:
fs.readdirAsync = function(dir) {
return new Promise(function(resolve, reject) {
fs.readdir(dir, function(err, data) {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
}
fs.statAsync = function(f) {
return new Promise(function(resolve, reject) {
fs.stat(f, function(err, data) {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
}
function filterFiles() {
return fs.readdirAsync(rootDir).then(function(files) {
let fileArray = [];
let dirArray = [];
// filter out entries that start with .
files = files.filter(function(f) {
return !f.startsWith(".");
});
return Promise.all(files.map(function(f) {
return fs.statAsync(f).then(function(stats) {
if (stats.isDirectory()) {
dirArray.push(f);
} else {
fileArray.push(f);
}
});
})).then(function() {
// make the resolved value be an object with two properties containing the arrays
return {dirArray, fileArray};
});
});
}
filterFiles().then(function(data) {
res.json(data);
}).catch(function(err) {
res.status(500).end();
});