Use control flow on async method with node.js and async.js - javascript

I'm trying to change a working code that doesn't have any control flow to display when everything is done how much change were done. WIthout control flow, it's not possible.
To do so, I'm trying async, but I'm a bit confused here.
Old code:
public static populate(){
// Read all files inside the populate folder.
fs.readdir(__config.path.populate, function(err, files){
if(err){
consoleDev(err);
}else{
// For each file, get the data to populate stored in the file.
_.each(files, function(file){
_.each(require(__config.path.base + __config.path.populate + file), function(documents){
// The file name must be the same as the targeted model.
var Model = __Dao.getModel(file.split('.')[0]);// Filter the extension.
if(Model){
// For each data, try to add it if it does not exist in the DB.
Model.findOrCreate(documents, function(err, doc, created){
if(created){
consoleDev('The following document was created in the [' + Model.modelName + '] collection: ' + JSON.stringify(doc));
}
});
}else{
consoleDev('Unable to populate the file: ' + file + '. The model was not found.')
}
});
});
}
});
}
New code:
public static populate(){
// Read all files inside the populate folder.
fs.readdir(__config.path.populate, function(err, files){
if(err){
consoleDev(err);
}else{
_.each(files, function(file){
// Read all files in async mode with control flow.
async.each(require(__config.path.base + __config.path.populate + file), function(documents, callback){
// The file name must be the same as the targeted model.
var Model = __Dao.getModel(file.split('.')[0]);// Filter the extension.
if(Model){
// For each data, try to add it if it does not exist in the DB.
Model.findOrCreate(documents, function(err, doc, created){
if(created){
consoleDev('The following document was created in the [' + Model.modelName + '] collection: ' + JSON.stringify(doc));
Populate._added++;
}else{
Populate._read++;
}
callback();
});
}else{
callback('Unable to populate the file: ' + file + '. The model was not found.')
}
});
}, function(err){
if(err){
console.log(err);
}
consoleDev(Populate._added + ' documents were added. There are now ' + Populate._read + ' + ' + Populate._added + ' = ' + (Populate._read + Populate._added) + ' documents populated.');
});
}
});
}
Nothing is showed on the console at the end and I don't have any error on the console. The script still works and add documents that wasn't present in the DB before. But I still don't have a message saying me how much were added.
I tried to do async.each(files, function(file, callback){ instead but I get the error Error: Callback was already called..
What am I missing here?

Related

Node.js - How do I use a complex function in HTML like "addUser"

I was given an assignment in class for making a basic ReSTFul Web Application, and received a sample to create mine off of, but only 2 of the 4 routes worked and I'm very new so I can't figure out why the other functions aren't working. The code looks like:
//setup
var express = require('express');
var app = express();
var fs = require("fs");
//run the server
var server = app.listen(8081, function () {
var host = server.address().address
var port = server.address().port
console.log("Example app listening at http://%s:%s", host, port)
})
//http://localhost:8081
//general route
//data for existing users located in "users.json"
//here is the refer
app.get("/", function(req,res){
var msg=""
msg += "<center><h1> This is the default page </h1></center>"
msg += " use the following <br />"
msg += " http://localhost:8081/listUsers <br />"
msg += " http://localhost:8081/addUser <br />"
msg += " http://localhost:8081/deleteUser <br />"
msg += " http://localhost:8081/(Put id# here) <br />"
res.send(msg);
});
//To find a list of users
app.get('/listUsers', function (req, res) {
fs.readFile( __dirname + "/" + "users.json", 'utf8', function (err, data) {
console.log( data );
res.end( data );
});
})
//To add a user to the list
var user = {
"user4" : {
"name" : "mohit",
"password" : "password4",
"profession" : "teacher",
"id": 4
}
}
app.post('/addUser', function (req, res) {
fs.readFile( __dirname + "/" + "users.json", 'utf8', function (err, data) {
//First read existing users.
data = JSON.parse( data );
data["user4"] = user["user4"];
console.log( data );
res.end( JSON.stringify(data));
});
})
//to show details of user by id#
app.get('/:id', function (req, res) {
// First read existing users.
fs.readFile( __dirname + "/" + "users.json", 'utf8', function (err, data) {
var users = JSON.parse( data );
var user = users["user" + req.params.id]
console.log( user );
res.end( JSON.stringify(user));
});
})
var id = 2;
//to delete a user
app.delete('/deleteUser', function (req, res) {
// First read existing users.
fs.readFile( __dirname + "/" + "users.json", 'utf8', function (err, data) {
data = JSON.parse( data );
delete data["user" + 2];
console.log( data );
res.end( JSON.stringify(data));
});
})
The functions for listing users and specifying users work, but the addUser and deleteUser say "unspecified," leading me to believe that the ( data ) part may not be properly specified. But I don't know specifically how I would specify a function.
Okay, there are a few issues going on here. First of all, it helps to walk through your code line by line to see what its doing, so you can try to figure out what you're missing:
You're reading the contents of users.json as a string using fs.readFile, which gets put in the variable "data" in your callback function.
You're converting "data" from a string to an object using JSON.parse. So far, so good.
You're setting the property "user4" on data, which should now be a list of users. This may or may not be correct depending on what the structure is for your users - if its an array, this code won't work. If its an object where each key is the username, you'll be fine here. Also, potential problem - you're setting the same key on the data object every time this request is made. You will continually overwrite the "user4" property each time, which will act as an update instead of an add. There's no way to determine what this code should be without see what you're POSTing into this API.
You're setting data["user4"] equal to user["user4"], or more specifically the value of the "user4" property of user. First issue - user is not defined anywhere. If this was data that was sent in the body of the POST, you'll want to read it from the body - probably something like (again, dependent on the format of data you're sending during your POST):
data["user4"] = req.body.user;
You're logging the full list of users. No problem here, good for visibility while debugging.
You're sending back the list of users you read from the file, plus the single user you just added.
There's a step missing here - you never saved the updated list of users in any way. Some type of data should be returned to the user, but the next time you call add or get (or any other method), the user you just defined won't be present, since it was never added to users.json. Here's a great post on how to write a string to a file: Writing files in Node.js
It will probably end up looking like this:
fs.readFile( __dirname + "/" + "users.json", 'utf8', function (err, data) {
//First read existing users.
data = JSON.parse( data );
data["user4"] = user["user4"];
console.log( data );
// Convert your updated user object into a JSON string
var strData = JSON.stringify(data);
// Write the updated JSON string out to the file
fs.writeFile(__dirname + "/" + "users.json", strData, function(err) {
if(err) {
return console.log(err);
}
console.log("The file was saved!");
});
res.end( JSON.stringify(data));
});
This covers the addUser endpoint, but you'll need to introduce similar changes for your deleteUser endpoint. If you're having trouble past this point, I'd recommend adding the contents of users.json to your question, as well as adding more detail on what you get back when you make the call into addUser and deleteUser (you can view the response body in Chrome dev tools -> Network tab).

RestAPI's deleting a model with another model

EDIT: I've just been informed by my professor not to access the driver from within the admin. Should be inside driver only.
I'm working on a project using restAPI's and a requirement is that an admin can delete driver.
They each both have their own schema and model in their own .js file
var mongoose = require('mongoose');
var AdminSchema = new mongoose.Schema({
name: String
});
mongoose.model('Admin', AdminSchema); // Model name is 'Admin'
module.exports = mongoose.model('Admin'); // Export for use in other parts of program
Similarly for the driver...
I'm using Postman to test all of this,
Now i'm stuck trying to have my admin be able to delete from both the admin database and the driver database using their ID's
the DELETE method for the admin looks like:
var Admin = require('./Admin');
var Driver = require('./Driver');
.
.
.
router.delete('/:id', function (req, res) {
Admin.findByIdAndRemove(req.params.id, function (err, admin) {
if (err) return res.status(500).send("There was a problem deleting the admin.");
res.status(200).send("Admin: " + admin.name + " was deleted.");
});
});
I've tried many things, including the method looking like this
router.delete('/:id', function (req, res) {
Admin.findByIdAndRemove(req.params.id, function (err, admin) {
if (err) return res.status(500).send("There was a problem deleting the admin.");
res.status(200).send("Admin: " + admin.name + " was deleted.");
});
Driver.findByIdAndRemove(req.params.id, function (err, driver) {
if (err) return res.status(500).send("There was a problem deleting the driver.");
res.status(200).send("Driver: " + driver.name + " was deleted.");
});
});
But this doesn't work, and I've tried having the methods separately, but the program only looks for whichever one is first, so if I have the method to delete the driver before the admin's delete method, it will find drivers, but not admins. It simply triggers the driver's error message and doesn't trigger the admins delete method.
Any tips or suggestions would be greatly appreciated. This is my first time learning about this stuff, and it's very interesting, but it's quite tricky!
Thanks!
The way you call the DB queries is incorrect. Both calls Admin.findByIdAndRemove and Driver.findByIdAndRemove are asynchronous so you need to treat them as such. You also can't call res.status(200).send(.. twice from the route handler. It should cause an error "Can't send headers after they were already sent" or something like that.
One way could be:
router.delete('/:id', function (req, res) {
var response = '';
Driver.findByIdAndRemove(req.params.id, function (err, driver) {
if (err)
response += "There was a problem deleting the driver.";
else
response += "Driver: " + driver.name + " was deleted.";
Admin.findByIdAndRemove(req.params.id, function (err, admin) {
if (err)
response += "There was a problem deleting the admin.";
else
response += ("Admin: " + admin.name + " was deleted.";
// you can't use error status since both db results could be different
res.status(200).send(response);
});
});
});
Also note that making multiple queries makes it difficult to use corect http status since each db query could have diferent result
Better way would be to have 2 routes one for admin and the other for driver
router.delete('/driver/:id', function (req, res) {
Driver.findByIdAndRemove(req.params.id, function (err, driver) {
if (err) return res.status(500).send("There was a problem deleting the driver.");
res.status(200).send("Driver: " + driver.name + " was deleted.");
});
});
router.delete('/admin/:id', function (req, res) {
Admin.findByIdAndRemove(req.params.id, function (err, admin) {
if (err) return res.status(500).send("There was a problem deleting the admin.");
res.status(200).send("Admin: " + admin.name + " was deleted.");
});
});

Reading and Writing files async in node.js

Currently i'm reading and writing files asynchronously, the thing is that i'm not sure that all the line from the file were read before the rest of my code is executed, here is my code:
var fs = require('fs');
//var str = fs.readFileSync("stockststs.json");
function updateJson(ticker) {
//var stocksJson = JSON.parse(fs.readFileSync("stocktest.json"));
fs.readFile('stocktest.json', function(error, file) {
stocksJson = JSON.parse(file);
if (stocksJson[ticker]!=null) {
console.log(ticker+" price : " + stocksJson[ticker].price);
console.log("changin the value...")
stocksJson[ticker].price = 989898;
console.log("Price after the change has been made -- " + stocksJson[ticker].price);
fs.writeFile('stocktest.json',JSON.stringify(stocksJson, null, 4) , function(err) {
if(!err) {
console.log("File successfully written");
}
});
}
else {
console.log(ticker + " doesn't exist on the json");
}
});
}
updateJson("APPL");
I'm wondering if there is any better way to to implement ?
its always a good practice to check in your callback for error. For example, i have written a small getCache function that checks if the file exists first, then tries to read that file and fire a callback afterwards
Cache.prototype.getCache = function(cache_filename, callback) {
cache_filename = this.cache_foldername + cache_filename;
fs.exists(cache_filename, function(exists) {
if (exists) {
fs.readFile(cache_filename, 'utf8', function (error, data) {
if (!error) {
console.log('File: '+ cache_filename +' successfully Read from Cache!');
_.isObject(data) ? callback(null,data) : callback(null,JSON.parse(data));
} else console.log("Error Reading Cache from file: " + cache_filename + " with Error: " + error);
});
} else callback(null,false);
});
}
note that in here what you need to do is to write the file after it has been read and checked with the if (!error)
i hope this helps

Finally handler when using 'all' aggregation function on an array of promises

I have some code that opens and reads a SQL script file and opens a connection to the database in parallel, when they are done it executes the contents of the file on the connection. However, the connection result needs to be 'closed'. I need to be able to handle the case where the database connection succeeds and reading the file fails (wrong filename perhaps) and still close the connection in either case.
Here is the code I am currently using, it has a finally() handler to close the client if the query succeeds or fails, but if the file read fails the client will not be closed.
function execFile(db, file) {
console.log('Connecting to ' + db);
return Promise.all([
connect('postgres://' + credentials + host + '/' + db),
fs.readFileAsync(file, 'utf8')
]).spread(function(client, initSql) {
console.log('Connected to ' + db);
console.log('Running init script ' + file);
return client.queryAsync(initSql).finally(client.end);
});
}
I played a little with the bind() function to pass the client into the finally block but I'm not very happy with the complexity that it introduces. I have a feeling that settle() could be useful here and I'm playing with that now.
What is the best way to handle this?
function execFile(db, file) {
console.log('Connecting to ' + db);
var client = connect('postgres://' + credentials + host + '/' + db);
var initSql = fs.readFileAsync(file, 'utf8');
return Promise.all([client, initSql]).spread(function(client, initSql) {
console.log('Connected to ' + db);
console.log('Running init script ' + file);
return client.queryAsync(initSql);
}).finally(function() {
if (client.isFulfilled()) client.inspect().value().end();
});
}

Crypto module - Node.js

Which is the simplest way to compare a hash of a file without storing it in a database?
For example:
var filename = __dirname + '/../public/index.html';
var shasum = crypto.createHash('sha1');
var s = fs.ReadStream(filename);
s.on('data', function(d) {
shasum.update(d);
});
s.on('end', function() {
var d = shasum.digest('hex');
console.log(d + ' ' + filename);
fs.writeFile(__dirname + "/../public/log.txt", d.toString() + '\n', function(err) {
if(err) {
console.log(err);
} else {
console.log("The file was saved!");
}
});
});
The above code returns the hash of the HTML file. If I edit the file how can I know if it has been changed? In other words, how can I know if the hash has been changed?
Any suggestions?
Edited
Now the hash is being saved in the log file. How can I retrieve the hash from the file and match it with the new generated one? A code example would be awesome to give me a better understanding.
There is no difference with this question, but it isn't clear for me yet how to implement it.
If you're looking for changes on a file, then you can use one of Node's filesystem functions, fs.watch. This is how it's used:
fs.watch(filename, function (event, filename) {
//event is either 'rename' or 'change'
//filename is the name of the file which triggered the event
});
The watch function is however not very consistent, so you can use fs.watchFile as an alternative. fs.watchFile uses stat polling, so it's quite a bit slower than fs.watch, which detects file changes instantly.
Watching a file will return an instance of fs.FSWatcher, which has the events change and error. Calling .close will stop watching for changes on the file.
Here's an example relating to your code:
var filename = __dirname + '/../public/index.html';
var shasum = crypto.createHash('sha1');
var oldhash = null;
var s = fs.ReadStream(filename);
s.on('data', function(d) {
shasum.update(d);
});
s.on('end', function() {
var d = shasum.digest('hex');
console.log(d + ' ' + filename);
oldhash = d.toString();
fs.writeFile(__dirname + "/../public/log.txt", d.toString() + '\n', function(err) {
if(err) {
console.log(err);
}
else {
console.log("The file was saved!");
}
});
});
//watch the log for changes
fs.watch(__dirname + "/../public/log.txt", function (event, filename) {
//read the log contents
fs.readFile(__dirname + "/../public/log.txt", function (err, data) {
//match variable data with the old hash
if (data == oldhash) {
//do something
}
});
});
What's the difference between this question and the previous one you asked? If you're not wanting to store it in a database, then store it as a file. If you want to save the hash for multiple files, then maybe put them in a JSON object and write them out as a .json file so they're easy to read/write.
EDIT
Given what you added to your question, it should be pretty simple. You might write a function to do check and re-write:
function updateHash (name, html, callback) {
var sha = crypto.createHash('sha1');
sha.update(html);
var newHash = sha.digest('hex');
var hashFileName = name + '.sha';
fs.readFile(hashFileName, 'utf8', function (err, oldHash) {
var changed = true;
if (err)
console.log(err); // probably indicates the file doesn't exist, but you should consider doing better error handling
if (oldHash === newHash)
changed = false;
fs.writeFile(hashFileName, newHash, { encoding: 'utf8' }, function (err) {
callback(err, changed);
});
});
}
updateHash('index.html', "<html><head><title>...", function (err, isChanged) {
// do something with this information ?
console.log(isChanged);
});

Categories