better way of writing nodejs function of readfile/write file - javascript

How can i write this code in a better way.
var fs = require('fs');
var file = '/test.txt';
fs.readFile(file, 'utf8', function (err, txt) {
if (err) return console.log(err);
txt = txt + '\nAppended something!';
fs.writeFile(myFile, txt, function (err) {
if(err) return console.log(err);
console.log('Appended text!');
});
});
Suppose i have multiple callback then how can we prevent this callback of callback and so on....
getData(function(a){
getMoreData(a, function(b){
getMoreData(b, function(c){
getMoreData(c, function(d){
getMoreData(d, function(e){
...
});
});
});
});
});

I really like bluebird for this:
First you have to 'promisify' fs. n the example below they directly promisify the readFile method:
var readFile = Promise.promisify(require("fs").readFile);
readFile("myfile.js", "utf8").then(function(contents) {
return eval(contents);
}).then(function(result) {
console.log("The result of evaluating myfile.js", result);
}).catch(SyntaxError, function(e) {
console.log("File had syntax error", e);
//Catch any other error
}).catch(function(e) {
console.log("Error reading file", e);
});
or:
var fs = Promise.promisifyAll(require("fs"));
// note now you have to put 'async' after the methods like so:
fs.readFileAsync("myfile.js", "utf8").then(function(contents) {
console.log(contents);
}).catch(function(e) {
console.error(e.stack);
});

I suggest async waterfall
Your first snippet would look like following:
var txt;
async.waterfall([
function(callback) {
fs.readFile(file, 'utf8', callback);
},
function(txt, callback) {
txt = txt + '\nAppended something!';
fs.writeFile(myFile, txt, callback);
},
function(callback) {
console.log('Appended text!');
callback();
}
], function (err, result) {
console.log(err)
});

What you're describing is callback hell and there's a couple of smart ways to get around it. I don't claim to be the know it all but there's a whole website called callbackhell.com that you might want to check out.
But for the short answer, you can do these things
1. keep your code shallow, or name your functions
Instead of writing your fs.readFile with an anonymous function, name it and call it like so
fs.readFile(file, 'utf8', function readFileCb(err, txt) {
if (err) throw new Error(err);
txt = txt + '\nAppended something!';
fs.writeFile(myFile, txt, function (err) {
// no need to return a console.log, just throw Error should suffice
if(err) throw new Error(err);
console.log('Appended text!');
});
});
2. Modularize your code. Have named functions or libraries that do exactly one thing
function writeFile(file, txt, cb){
fs.writeFile(file, txt, cb)
}
function writeFileCb(err){
if(err) throw new Error(err);
console.log('Appended Text!');
}
fs.readFile(file, 'utf8', function readFileCb(err, txt) {
if (err) throw new Error(err);
txt = txt + '\nAppended something!';
writeFile(myFile, txt, writeFileCb);
});
3. Ensure that all errors are caught. You seem to be doing that well, so kudos!
You can also use Promises, libraries like Async waterfall, but callbacks are an essential parts of JavaScript and going through callback hell is just a matter of having good sense in writing your code.

Related

Why does asynchronous writeFile method behave synchronously?

I have a simple JS file that reads a text file and the writes it and reads the changed file. For learning purposes, I have implemented the below code.
When the control reaches writeFile, shouldn't it be running in the background and the control should go to the console.log("Test") line and then back to the writeFile? But it actually fully executes the writeFile and control goes to the log line which is basically a synchronous behavior. Am I missing something here ?
console.log("Begin")
var fs = require("fs");
fs.readFile("input.txt", function(err, data) {
if (err) {
console.log(err);
} else {
console.log("Inside Read " + data.toString());
fs.writeFile("input.txt", "Replaced Text", function(err) {
if (err) {
console.log(err);
} else {
console.log("Inside Write");
var data = fs.readFileSync("Input.txt") console.log(data + " is the replaced text");
}
console.log("Test2");
});
}
});
console.log("Ended");
console.log("Test2") is inside of the writeFile callback function and will not run until writeFile is completed.
var fs = require("fs");
fs.readFile("input.txt", function(err, data) {
console.log("This will second, after print after readFile is complete);
fs.writeFile("input.txt", "Replaced Text", function(err) {
console.log("This will print last, after writeFile is complete");
});
console.log("This will print third, before writeFile is complete");
});
console.log("This will print first");

Return final value from traversing folder function

Question: Why won't a var things return a value from outside the walk() function? And how do I fix it?
Hypothosis: this is async and the console.log is happening too early. Which would lead me to how can I make this a Promise (i'm using node 4.1.1)
var walk = function(dir, done) {
var results = [];
fs.readdir(dir, function(err, list) {
if (err) return done(err);
var i = 0;
(function next() {
var file = list[i++];
if (!file) return done(null, results);
file = dir + '/' + file;
fs.stat(file, function(err, stat) {
if (stat && stat.isDirectory()) {
walk(file, function(err, res) {
results = results.concat(res);
next();
});
} else {
results.push(file);
next();
}
});
})();
});
};
function traverseDirectories() {
var things = walk('src/', function(err, results){
if(err) throw err;
console.log(results) // ['dir/thing.png', 'dir/thing2.png']
return results;
});
console.log(things) // undefined
};
traverseDirectories();
Q. Why won't a var things return a value from outside the walk() function?
R. Because walk doesn't return anything (take a look and you''ll see that it's a void function).
Even if you make it a Promise, you won't be able to use it like:
var things = walk(...);
console.log(things);
Because Promises are thenable, and are still async, so it will be:
walk(...).then(function(things) {
// do something with things here
});
To do what you want, you would need something that doesn't exist in current Javascript yet.
There is an ES7 proposal of native async/await that will be a callback heaven, but atm, you can use:
Async/Await library (It's an amazing library, but very far from native, and performance isn't cool)
ES7 transpiler - you can write the ES7 code today, and it will transpile for you to ES5 (e.g Babel)
But, if you're already using the newest version of NodeJS (4.0.0 as the time of writing) - and if you're not, you really should - the best way of achieving what you want is to use generators.
Combined with a small library named co, it will help you to achieve almost what the ES7 async/await proposes, and it will mostly use native code, so both readability and performance are really good:
var co = require('co');
var traverseDirectories = co(function *traverseDirectories() {
var things = yield walk('src/');
console.log(things) // there we go!
});
function walk(dir, results) {
return new Promise(function(resolve, reject) {
fs.readdir(dir, function(err, list) {
if (err)
reject(err);
var i = 0;
(function next() {
var file = list[i++];
if (!file) resolve(results);
file = dir + '/' + file;
fs.stat(file, function(err, stat) {
if (stat && stat.isDirectory()) {
walk(file).then(function(res) {
results = results.concat(res);
next();
});
} else {
results.push(file);
next();
}
});
})();
});
});
}
You can read more about this subject in this awesome Thomas Hunter's blog post.

How to rewrite asynchronous function (node.js, monk)

I'm trying to write an asynchronous function to give me a random document from a mongodb collection.
var getRandDoc = function(){
var db = monk('localhost/data');
var coll = db.get('coll');
coll.count({}, function(err, count){
if (err) console.log(err);
else {
coll.find({}, {limit:-1, skip:randomNum(0, count)}, function(err, out){
if (err) console.log(err);
else{
db.close();
return out[0]['name'];
}
});
}
});
}
In another file, I call this function with something like:
console.log(test.getRandDoc());
And I get undefined
What am I doing wrong and how do I fix it?
It's the usual node callback confusion. If you don't want to use promises then getRandDoc() needs to accept a callback and then call it with the results in the coll.find(...) method. So something like this:
var getRandDoc = function(cb){
var db = monk('localhost/data');
var coll = db.get('coll');
coll.count({}, function(err, count){
if (err) console.log(err);
else {
coll.find({}, {limit:-1, skip:randomNum(0, count)}, function(err, out){
if (err) return cb(err)
else{
db.close();
return cb(null, out[0]['name']);
}
});
}
});
}
You probably want to pass back that error too, so:
test.getRandDoc(function(err, name){
});
A promise based version is going to look something like this:
var getRandDoc = function(){
var db = monk('localhost/data');
var coll = db.get('coll');
var deferred = Q.defer();
coll.count({}, function(err, count){
if (err) deferred.reject(err);
else {
coll.find({}, {limit:-1, skip:randomNum(0, count)}, function(err, out){
if (err) deferred.reject(err);
else{
db.close();
deferred.resolve((out[0]['name']);
}
});
}
});
return deferred.promise;
}
But it's still not going to give you a straight forward variable assignment. You'll wind up calling it something like this:
test.getRandDoc().then(function(res){}).fail(function(err){});
Welcome to node!

Nodejs mongoose Mass/Batch update from file

So i have a csv file containing my information, i need to do a mass add/update
exports.add_questions_from_file = function (file_path, surveyid, callback)
{
var U = [{}];
fs.readFile(file_path, 'utf8', function(err, data){
if (err){
console.log(err);
callback(err,null);
}else{
console.log(data);
d = data.split(/\r\n|\n/);
for (x=0;x <d.length;x++)
{
line = d[x].split(',');
if (line[0] == "") {return};
RQuestion.add_by_line (line,function (err, question)
{
U.push({id:question.id});
console.log(U);
});
}
}
});
Survey.update({_id:surveyid},{$push:{"SurveyQuestions":U}},function (err,numAffected, rawResponse) {
console.log(rawResponse);
RET = {"module":"survey","operation": "add", "status":"OK"};
callback(RET);
});
};
But even though im using callback functions the update seems to happen with the same object always, even the console.log here
U.push({id:question.id});
console.log(U);
returns the same object (even that all the other were created)
Im doing something wrong?
I see a few issues.
First for:
if (line[0] == "") {return};
Don't you mean to use a break or continue instead? Otherwise the entire function will quit if there is a blank line anywhere in the file. This is very important because Survey.update won't get called either.
Second: I assumed that RQuestion.add_by_line and Survey.update are doing something async like updating a database. Your code needs to be restructured to wait for those async items to complete before moving on to the next step. I'd recommend an npm package named async for that.
fs.readFile(file_path, 'utf8', function(err, data){
if (err){
console.log(err);
callback(err,null);
}else{
d = data.split(/\r\n|\n/);
async.map(d, function(line, callback) {
//this function is called for each line
add_by_line (line,function (err, question)
{
callback(err,{id:question.id});
});
}, function(err, results) {
//this function is called when all of the items are done
console.log("done with async");
console.dir(results);
Survey.update({_id:surveyid},{$push:{"SurveyQuestions":results},function (err,numAffected, rawResponse) {
console.log(rawResponse);
RET = {"module":"survey","operation": "add", "status":"OK"};
callback(RET);
});
});
}
});

Node.JS module.exports for passing parameter between two functions?

I am new to Node.js so this is probably a fundamental question and I am missing something obvious. In the code below, I am trying to set the sql_file name from foo but I keep getting an error on the file not existing because the variable is not working. If I hard code the file name in sql_util.js it works fine.
So how do I pass a parameter or any object from one js file into the function of another?
foo.js
var misc = require('./sql_get');
console.log(misc.sql_file("on.sql"));
sql_util.js
fs = require('fs');
file = 'on.sql'
function sql_file(cb) {
var fileName = "./SQLs/" + sql_file;
fs.readFile(fileName, function(err, buffer) {
if (err) return cb(err);
return cb(null, buffer.toString());
});
}
sql_file(function(err, sqlstatement) {
if (err) throw err;
console.log('sql statement is: ' + sqlstatement);
});
};
module.exports.x = x;
module.exports.sql_fil = sql_file;
Let's go through this line by line because I see a lot of errors, both syntax and semantic.
foo.js
var misc = require('./sql_get');
console.log(misc.sql_file("on.sql"));
You defined in the function below sql_file to be asynchronous, so it does not return a value, but takes a callback that it passes the result to.
sql_util.js
fs = require('fs');
file = 'on.sql'
You have an unused module-global variable file. Remove this. It's going to cause confusion.
function sql_file(cb) {
var fileName = "./SQLs/" + sql_file;
sql_file is a function. I can tell because this code lives in a function called sql_file. Therefore, fileName will be "./SQLs/" + the .toString() result of the function, which is the source of the function. I think perhaps you want a parameter?
fs.readFile(fileName, function(err, buffer) {
if (err) return cb(err);
return cb(null, buffer.toString());
});
}
This seems ok.
sql_file(function(err, sqlstatement) {
if (err) throw err;
console.log('sql statement is: ' + sqlstatement);
});
};
I am not sure what you are trying to do here. Why are you calling the function?
module.exports.x = x;
module.exports.sql_fil = sql_file;
Both of these lines have a problem. There is no symbol defined named x. I am surprised this code runs without throwing an error on that line. Second, you are exporting sql_file as sql_fil, missing the letter e.
The Solution
Since what you asked about is parameter passing and you only have a single function, let's simplify this. You never want to use global variables. If a function needs a particular variable, just pass it as an argument.
foo.js
var misc = require('./sql_get');
misc.sql_file('on.sql', function (err, contents) {
console.log(contents);
});
sql_get.js (notice the file is renamed)
var fs = require('fs');
function sql_file(sqlFilename, cb) {
var fileName = "./SQLs/" + sqlFilename;
fs.readFile(fileName, function(err, buffer) {
if (err) return cb(err);
return cb(null, buffer.toString());
});
}
module.exports.sql_file = sql_file;
A few problems:
You're requiring sql_get but naming the other file sql_util
var misc = require('./sql_util');
You're exporting module.exports.sql_fil = sql_file; (see the missing e). You probably mean;
module.exports.sql_file = sql_file;
While calling sql_file, you are passing a string but expecting a cb in the function itself -
misc.sql_file("on.sql", function(err, fileContent) {
if(err) return console.log(err);
console.log('File content: ', fileContent);
});
function sql_file(sqlFileName, cb) {
var fileName = "./SQLs/" + sqlFileName;
fs.readFile(fileName, function(err, buffer) {
if (err) return cb(err);
return cb(null, buffer.toString());
});
}
And I don't know what you are doing with calling sql_file function in that file itself. Remove that.

Categories

Resources