Why is my else statement run in this piece of node code? - javascript

I'm doing the learnyounode exercises. In this function, I list a directory and filter files by extension. As I understand, if I provide an invalid directory name, it should go into the if clause and exit. But instead, it goes to the if clause and then complains about something in the else clause.
Module:
module.exports = function (path, ext, callb) {
var fs = require('fs');
var elems = [];
var r = new RegExp("\\." + ext);
fs.readdir(path, function (err, list) {
console.log(err); // this runs, it's an object like { [Error: ENOENT, scandir '/Users/JohnnyLee/baobabf/
console.log(list); // this also runs, it's undefined
if (err) {
console.log("ERR!!!"); // this ran!
return callb(err);
} else {
console.log("NO ERRR!!!"); // this didn't run :/
list.forEach(function (i) { // this crashes?
if (i.match(r)) {
elems.push(i);
}
});
return callb(err, elems);
}
console.log(list);
});
};
Executable:
var mymod = require('./p06-1');
var filename = process.argv[2]
var extension = process.argv[3]
if (filename && extension) {
mymod(filename, extension, function (err, list) {
list.forEach(function (i) {
console.log(i);
});
})
}
The output:
triton:learnnode JohnnyLee$ node p06-2.js doesntexist txt
{ [Error: ENOENT, scandir 'doesntexist'] errno: -2, code: 'ENOENT', path: 'doesntexist' }
undefined
ERR!!!
/Users/JohnnyLee/learnnode/p06-2.js:7
list.forEach(function (i) {
^
TypeError: Cannot read property 'forEach' of undefined
at /Users/JohnnyLee/learnnode/p06-2.js:7:13
at /Users/JohnnyLee/learnnode/p06-1.js:11:20
at FSReqWrap.oncomplete (fs.js:95:15)
triton:learnnode JohnnyLee$
What did I miss :(?

You call it with:
mymod(filename, extension, function (err, list) {
list.forEach(function (i) {
console.log(i);
});
})
It is the list.forEach in there that errors. Look at the filename/line number for the error. This is because you call callb(err) in the module, but you don't check for an error before assuming there's a list to work with.

Please check the error stack properly, it's loud and clear :)
As for your question of what you've missed,
Add an error check statement to handle error.
Modify mymod call as shown below,
if (filename && extension) {
mymod(filename, extension, function (err, list) {
if(err){
//do something with error
}else{
list.forEach(function (i) {
console.log(i);
}
});
})
}

Related

Error: ENOTDIR: not a directory, scandir error on interactionCreate

All of my files from Commands are read in fine but I get an error from 'interactionCreate.jslocated inEvents`
node:internal/fs/utils:343
throw err;
^
Error: ENOTDIR: not a directory, scandir './Events/interactionCreate.js'
My Event.js file is as follows:
const { readdirSync } = require('fs');
const ascii = require('ascii-table');
let table = new ascii("Events");
table.setHeading('EVENTS', ' LOAD STATUS');
module.exports = (client) => {
readdirSync('./Events/').forEach(dir => {
const events = readdirSync(`./Events/${dir}`).filter(file => file.endsWith('.js'));
for(let file of events) {
let pull = require(`../Events/${dir}/${file}`);
if(pull.name) {
client.events.set(pull.name, pull);
} else {
table.addRow(file, 'EVENT REGISTERED')
continue;
} if(pull.aliases && Array.isArray(pull.aliases)) pull.aliases.forEach(alias => client.aliases.set(alias, pull.name))
}
});
console.log(table.toString());
}
Your problem is here:
readdirSync('./Events/').forEach(dir => {
const events = readdirSync(`./Events/${dir}`)
readdirSync will return all the entries in the Events dir, that includes both files and directories. You've named your variable dir but they aren't all dirs. This is evidenced by the error message which specifically states ./Events/interactionCreate.js is not a directory.
Either remove non-dirs from your Events directory (i.e. move that file), or better, check if dir is in fact a directory before calling readdirSync on it.
The easiest way to do that is to add the {withFileTypes: true} option, and then you can call dir.isDirectory()
See docs https://nodejs.org/api/fs.html#fsreaddirsyncpath-options

Bulk convert XML to JSON with particular output

How do I convert approximately 3000 XML files into JSON files using node?
I've been able to get a script below to a single XML to JSON file in the format that I want, and I've been attempting to promisify the script using bluebird, but I keep getting errors. I've been able to get the script below to list the filenames, but then I get the error "Unhandled rejection Error: ENOENT: no such file or directory, open 'journal-article-10.2307_357359.xml'"
var Promise = require('bluebird');
var fs = require('fs');
var convert = require('xml-js');
fs.readdirAsync = function(dirname) {
return new Promise(function(resolve, reject) {
fs.readdir(dirname, function(err, filenames){
if (err)
reject(err);
else
resolve(filenames);
});
});
};
fs.readFileAsync = function(filename, enc) {
return new Promise(function(resolve, reject) {
fs.readFile(filename, enc, function(err, data){
if (err)
reject(err);
else
resolve(data);
});
});
};
function getFile(filename) {
return fs.readFileAsync(filename, 'utf8');
}
fs.readdirAsync('./metadata/').then(function (filenames){
console.log(filenames);
return Promise.all(filenames.map(getFile));
}).then(function (files){
files.forEach(function(files){
function nativeType(value) {
var nValue = Number(value);
if (!isNaN(nValue)) {
return nValue;
}
var bValue = value.toLowerCase();
if (bValue === 'true') {
return true;
} else if (bValue === 'false') {
return false;
}
return value;
}
var removeJsonTextAttribute = function(value, parentElement) {
try {
var keyNo = Object.keys(parentElement._parent).length;
var keyName = Object.keys(parentElement._parent)[keyNo - 1];
parentElement._parent[keyName] = nativeType(value);
} catch (e) {}
};
var options = {
compact: true,
trim: true,
ignoreDeclaration: true,
ignoreInstruction: true,
ignoreAttributes: true,
ignoreComment: true,
ignoreCdata: true,
ignoreDoctype: true,
textFn: removeJsonTextAttribute,
spaces: 2
};
fs.writeFile("./json/" + fileaname + ".json", convert.xml2json(options));
});
});
I would like to be able to convert the entire folder of XML files to JSON (to upload to couchDB).
ENOENT is a standard POSIX error code that means the path to the file is not found. You've tried to open the name of a file or directory that doesn't exist. In this case, fs.readdir returns names that are not fully qualified file names, so you'll need to prefix them with the path that you gave it, specifically: './metadata/'. The error message that you're seeing tells you the file that was opened: journal-article-10.2307_357359.xml, but in this case, you probably want to open ./metadata/journal-article-10.2307_357359.xml.
You can see this with the following simple example:
# Create a dummy directory named `garbage` that contains only 3 entries
$ mkdir -p garbage/{foo,bar,baz}
# Run `node` interactively
$ node
> const fs = require('fs');
undefined
> fs.readdirSync('./garbage')
[ 'bar', 'baz', 'foo' ]
It doesn't make sense to open 'bar' since that doesn't exist. You'll need to open './garbage/bar' for it to work correctly.

Node.js read a file in a zip without unzipping it

I have a zip file (actually it's an epub file) I need to loop through the files in it and read them without unzipping them to the disk.
I tried to use a Node.js library called JSZip but the content of each file is stored in memory in Buffer and whenever I try to decode the buffer content to string the content returned is unreadable
Here's the code I tried:
const zip = new JSZip();
// read a zip file
fs.readFile(epubFile, function (err, data) {
if (err) throw err;
zip.loadAsync(data).then(function (zip) {
async.eachOf(zip.files, function (content, fileName, callback) {
if (fileName.match(/json/)) {
var buf = content._data.compressedContent;
console.log(fileName);
console.log((new Buffer(buf)).toString('utf-8'));
}
callback();
}, function (err) {
if (err) {
console.log(err);
}
});
});
});
Since unzip seems to be abandoned, I used node-stream-zip with pretty good success.
npm install node-stream-zip
Reading files be all like:
const StreamZip = require('node-stream-zip');
const zip = new StreamZip({
file: 'archive.zip',
storeEntries: true
});
zip.on('ready', () => {
// Take a look at the files
console.log('Entries read: ' + zip.entriesCount);
for (const entry of Object.values(zip.entries())) {
const desc = entry.isDirectory ? 'directory' : `${entry.size} bytes`;
console.log(`Entry ${entry.name}: ${desc}`);
}
// Read a file in memory
let zipDotTxtContents = zip.entryDataSync('path/inside/zip.txt').toString('utf8');
console.log("The content of path/inside/zip.txt is: " + zipDotTxtContents);
// Do not forget to close the file once you're done
zip.close()
});
npm install unzip
https://www.npmjs.com/package/unzip
fs.createReadStream('path/to/archive.zip')
.pipe(unzip.Parse())
.on('entry', function (entry) {
var fileName = entry.path;
var type = entry.type; // 'Directory' or 'File'
var size = entry.size;
if (fileName === "this IS the file I'm looking for") {
entry.pipe(fs.createWriteStream('output/path'));
} else {
entry.autodrain();
}
});

Looping through files in a folder Node.JS

I am trying to loop through and pick up files in a directory, but I have some trouble implementing it. How to pull in multiple files and then move them to another folder?
var dirname = 'C:/FolderwithFiles';
console.log("Going to get file info!");
fs.stat(dirname, function (err, stats) {
if (err) {
return console.error(err);
}
console.log(stats);
console.log("Got file info successfully!");
// Check file type
console.log("isFile ? " + stats.isFile());
console.log("isDirectory ? " + stats.isDirectory());
});
Older answer with callbacks
You want to use the fs.readdir function to get the directory contents and the fs.rename function to actually do the renaming. Both these functions have synchronous versions if you need to wait for them to finishing before running the code afterwards.
I wrote a quick script that does what you described.
var fs = require('fs');
var path = require('path');
// In newer Node.js versions where process is already global this isn't necessary.
var process = require("process");
var moveFrom = "/home/mike/dev/node/sonar/moveme";
var moveTo = "/home/mike/dev/node/sonar/tome"
// Loop through all the files in the temp directory
fs.readdir(moveFrom, function (err, files) {
if (err) {
console.error("Could not list the directory.", err);
process.exit(1);
}
files.forEach(function (file, index) {
// Make one pass and make the file complete
var fromPath = path.join(moveFrom, file);
var toPath = path.join(moveTo, file);
fs.stat(fromPath, function (error, stat) {
if (error) {
console.error("Error stating file.", error);
return;
}
if (stat.isFile())
console.log("'%s' is a file.", fromPath);
else if (stat.isDirectory())
console.log("'%s' is a directory.", fromPath);
fs.rename(fromPath, toPath, function (error) {
if (error) {
console.error("File moving error.", error);
} else {
console.log("Moved file '%s' to '%s'.", fromPath, toPath);
}
});
});
});
});
Tested on my local machine.
node testme.js
'/home/mike/dev/node/sonar/moveme/hello' is a file.
'/home/mike/dev/node/sonar/moveme/test' is a directory.
'/home/mike/dev/node/sonar/moveme/test2' is a directory.
'/home/mike/dev/node/sonar/moveme/test23' is a directory.
'/home/mike/dev/node/sonar/moveme/test234' is a directory.
Moved file '/home/mike/dev/node/sonar/moveme/hello' to '/home/mike/dev/node/sonar/tome/hello'.
Moved file '/home/mike/dev/node/sonar/moveme/test' to '/home/mike/dev/node/sonar/tome/test'.
Moved file '/home/mike/dev/node/sonar/moveme/test2' to '/home/mike/dev/node/sonar/tome/test2'.
Moved file '/home/mike/dev/node/sonar/moveme/test23' to '/home/mike/dev/node/sonar/tome/test23'.
Moved file '/home/mike/dev/node/sonar/moveme/test234' to '/home/mike/dev/node/sonar/tome/test234'.
Update: fs.promises functions with async/await
Inspired by ma11hew28's answer (shown here), here is the same thing as above but with the async functions in fs.promises. As noted by ma11hew28, this may have memory limitations versus fs.promises.opendir added in v12.12.0.
Quick code below.
//jshint esversion:8
//jshint node:true
const fs = require( 'fs' );
const path = require( 'path' );
const moveFrom = "/tmp/movefrom";
const moveTo = "/tmp/moveto";
// Make an async function that gets executed immediately
(async ()=>{
// Our starting point
try {
// Get the files as an array
const files = await fs.promises.readdir( moveFrom );
// Loop them all with the new for...of
for( const file of files ) {
// Get the full paths
const fromPath = path.join( moveFrom, file );
const toPath = path.join( moveTo, file );
// Stat the file to see if we have a file or dir
const stat = await fs.promises.stat( fromPath );
if( stat.isFile() )
console.log( "'%s' is a file.", fromPath );
else if( stat.isDirectory() )
console.log( "'%s' is a directory.", fromPath );
// Now move async
await fs.promises.rename( fromPath, toPath );
// Log because we're crazy
console.log( "Moved '%s'->'%s'", fromPath, toPath );
} // End for...of
}
catch( e ) {
// Catch anything bad that happens
console.error( "We've thrown! Whoops!", e );
}
})(); // Wrap in parenthesis and call now
fs.readdir(path[, options], callback) (which Mikey A. Leonetti used in his answer) and its variants (fsPromises.readdir(path[, options]) and fs.readdirSync(path[, options])) each reads all of a directory's entries into memory at once. That's good for most cases, but if the directory has very many entries and/or you want to lower your application's memory footprint, you could instead iterate over the directory's entries one at a time.
Asynchronously
Directories are async iterable, so you could do something like this:
const fs = require('fs')
async function ls(path) {
const dir = await fs.promises.opendir(path)
for await (const dirent of dir) {
console.log(dirent.name)
}
}
ls('.').catch(console.error)
Or, you could use dir.read() and/or dir.read(callback) directly.
Synchronously
Directories aren't sync iterable, but you could use dir.readSync() directly. For example:
const fs = require('fs')
const dir = fs.opendirSync('.')
let dirent
while ((dirent = dir.readSync()) !== null) {
console.log(dirent.name)
}
dir.closeSync()
Or, you could make directories sync iterable. For example:
const fs = require('fs')
function makeDirectoriesSyncIterable() {
const p = fs.Dir.prototype
if (p.hasOwnProperty(Symbol.iterator)) { return }
const entriesSync = function* () {
try {
let dirent
while ((dirent = this.readSync()) !== null) { yield dirent }
} finally { this.closeSync() }
}
if (!p.hasOwnProperty(entriesSync)) { p.entriesSync = entriesSync }
Object.defineProperty(p, Symbol.iterator, {
configurable: true,
enumerable: false,
value: entriesSync,
writable: true
})
}
makeDirectoriesSyncIterable()
And then, you could do something like this:
const dir = fs.opendirSync('.')
for (const dirent of dir) {
console.log(dirent.name)
}
Note: "In busy processes, use the asynchronous versions of these calls. The synchronous versions will block the entire process until they complete, halting all connections."
References:
Node.js Documentation: File System: Class fs.Dir
Node.js source code: fs.Dir
GitHub: nodejs/node: Issues: streaming / iterative fs.readdir #583
Read all folders in a directory
const readAllFolder = (dirMain) => {
const readDirMain = fs.readdirSync(dirMain);
console.log(dirMain);
console.log(readDirMain);
readDirMain.forEach((dirNext) => {
console.log(dirNext, fs.lstatSync(dirMain + "/" + dirNext).isDirectory());
if (fs.lstatSync(dirMain + "/" + dirNext).isDirectory()) {
readAllFolder(dirMain + "/" + dirNext);
}
});
};
The answers provided are for a single folder. Here is an asynchronous implementation for multiple folders where all the folders are processed simultaneously but the smaller folders or files gets completed first.
Please comment if you have any feedback
Asynchronously Multiple Folders
const fs = require('fs')
const util = require('util')
const path = require('path')
// Multiple folders list
const in_dir_list = [
'Folder 1 Large',
'Folder 2 Small', // small folder and files will complete first
'Folder 3 Extra Large'
]
// BEST PRACTICES: (1) Faster folder list For loop has to be outside async_capture_callback functions for async to make sense
// (2) Slower Read Write or I/O processes best be contained in an async_capture_callback functions because these processes are slower than for loop events and faster completed items get callback-ed out first
for (i = 0; i < in_dir_list.length; i++) {
var in_dir = in_dir_list[i]
// function is created (see below) so each folder is processed asynchronously for readFile_async that follows
readdir_async_capture(in_dir, function(files_path) {
console.log("Processing folders asynchronously ...")
for (j = 0; j < files_path.length; j++) {
file_path = files_path[j]
file = file_path.substr(file_path.lastIndexOf("/") + 1, file_path.length)
// function is created (see below) so all files are read simultaneously but the smallest file will be completed first and get callback-ed first
readFile_async_capture(file_path, file, function(file_string) {
try {
console.log(file_path)
console.log(file_string)
} catch (error) {
console.log(error)
console.log("System exiting first to catch error if not async will continue...")
process.exit()
}
})
}
})
}
// fs.readdir async_capture function to deal with asynchronous code above
function readdir_async_capture(in_dir, callback) {
fs.readdir(in_dir, function(error, files) {
if (error) { return console.log(error) }
files_path = files.map(function(x) { return path.join(in_dir, x) })
callback(files_path)
})
}
// fs.readFile async_capture function to deal with asynchronous code above
function readFile_async_capture(file_path, file, callback) {
fs.readFile(file_path, function(error, data) {
if (error) { return console.log(error) }
file_string = data.toString()
callback(file_string)
})
}

storing files in mongodb using gridfs and mongoose

I have the following code
gridfs.js has the following code that writes file bytes
exports.putFile = function(path, name, options, fn) {
var db;
db = mongoose.connection.db;
options = parse(options);
options.metadata.filename = name;
return new GridStore(db, name, "w", options).open(function(err, file) {
if (err) {
return fn(err);
}
return file.writeFile(path, fn);
});
};
The mongoose schema is defined below. The mongoose schema has a file name and the file itself.
var fs = require('fs');
var mongoose = require('mongoose');
var db = mongoose.connection;
var gridfs = require('./gridfs');
var Schema = mongoose.Schema;
var trackDocsSchema = mongoose.Schema(
{
_id : Schema.Types.ObjectId,
docName: 'string',
files: [ mongoose.Schema.Mixed ]
}
);
trackDocsSchema.methods.addFile = function(file, options, fn) {
var trackDocs;
trackDocs = this;
return gridfs.putFile(file.path, file.name, options, function(err, result) {
if (err) console.log("postDocsModel TrackDocs Error: " + err);
trackDocs.files.push(result);
return trackDocs.save(fn);
});
};
var TrackDocs = mongoose.model("TrackDocs", trackDocsSchema);
The server code that is invoked is below.
exports.uploadFile = function (req, res) {
console.log("uploadFile invoked");
var trackDocs, opts;
trackDocs = new TrackDocs();
trackDocs._id = mongoose.Types.ObjectId(req.body._id);
trackDocs.docName = req.body.docName;
opts = {
content_type: req.files.file.type
};
return trackDocs.addFile(req.files.file, opts, function (err, result) {
if (err) console.log("api TrackDocs Error: " + err);
console.log("Result: " + result);
trackDocs.save();
console.log("Inserted Doc Id: " + trackDocs._id);
return res.json(true);
});
};
});
When I run the folowing code, I get an error
api TrackDocs Error: RangeError: Maximum call stack size exceeded and nothing is added to GridFS. However, if I upload the same file again, it does save it into GridFS.
Notice there are two trackDocs.saves. I am not sure if that is causing the issue or something else.
I am new to NodeJS and Mongoose so any assitance will be much appreciated.
Thanks,
Melroy
Not sure whether this helps or not, but it has helped me in the past.
Not surprisingly, different browsers have different call stack sizes. Also not surprisingly, the method that they use to determine the call stack varies as well. The various call stack sizes I could measure are (give or take, might be off by 1 or 2):
We ran some tests ourselves in order to get the call stack sizes on other browsers/hosts/OSs using this simple code:
var i = 0;
function recurse () {
i++;
recurse();
}
try {
recurse();
} catch (ex) {
alert('i = ' + i + '\nerror: ' + ex);
}
http://javascriptrules.com/2009/06/30/limitation-on-call-stacks/
In saying that an option could be passing --max-stack-size to node.
Set max v8 stack size (bytes)
node --max-stack-size 32000 app.js
NOTE: Help prints it as --max-stack-size, in node -v v0.10.x+ you need to use --stack-size instead.

Categories

Resources