node.js For i in files render item - node.js

I want to repeat some items in my template (twig) on different places, where each repeated item responds to file names in different directories, so first I need an array with filenames.
I don't know how to affect outer scope from function. This will not work:
var files_list = {'json': [], 'js': []}
fs.readdir('./public/json', function(err, items) {
if (err) throw err;
files_list.json = items;
});
res.render('index', { js: files_list.js, jsons: files_list.json });
This works, but how to render it again to get something from another directory.
fs.readdir('./public/json', function(err, items) {
if (err) throw err;
res.render('index', { jsons: items });
});

This is a var that you does't have synchronously at hand; So you should treat it the asynchronous way:
Use a callback function:
var getFiles = function(dir, cb) {
fs.readdir(dir, function(err, files) {
if (err) cb(err, null);
cb(null, files);
});
};
Usage:
getFiles('./public/json', function(err, files) {
if (err) throw err;
res.render('index', { jsons: files });
});
Or a promise:
var getFiles = function(dir) {
return new Promise(function(resolve, reject) {
fs.readdir(dir, function(err, files) {
if (err) reject(err);
resolve(files);
});
});
};
Usage:
getFiles('./public/json')
.then(function(files) {
res.render('index', { jsons: files });
}, function(err) {
throw err;
});

Related

Mongodb.connect does not execute callback function

Im trying to connect to my Mongodb and insert some documents if they are not already in the db. It works fine with the first inserts but in the function existInDatabase it sometimes does not execute the callback function.
var MongoClient = require('mongodb').MongoClient;
var mongoData = require('./mongoData');
var exports = module.exports = {};
var dbName = 'checklist';
MongoClient.connect(mongoData.ConString, {
useNewUrlParser: true
}, function(err, db) {
if (err) throw err;
for (var key in mongoData.Customers) {
if (!existsInDatabase(mongoData.Customers[key], 'Customers')) {
db.db(dbName).collection('Customers').insertOne(mongoData.Customers[key], function(err, res) {
if (err) throw err;
console.log('1 document inserted');
db.close();
});
}
}
for (var key in mongoData.Categorys) {
if (!existsInDatabase(mongoData.Customers[key], 'Customers')) {
db.db(dbName).collection('Categorys').insertOne(mongoData.Categorys[key], function(err, res) {
if (err) throw err;
console.log('1 document inserted');
db.close();
});
}
}
});
function existsInDatabase(obj, collection) {
var result = false;
MongoClient.connect(mongoData.ConString, {
useNewUrlParser: true
}, function(err, db) {
db.db(dbName).collection(collection).find({}).forEach(function(doc) {
if (doc.id == obj.id) {
result = true;
}
}, function(err) {
console.log(err);
});
});
return result;
}
I have made a few changes to your code. It seems you are new to async programming, spend some time to understand the flow. Feel free for any further query. Here is your code.
// Welcome to aync programming
// Here no one waits for the slow processes
var MongoClient = require('mongodb').MongoClient;
var mongoData = require('./mongoData');
var exports = module.exports = {};
var dbName = 'checklist';
// Make the connection for once only
MongoClient.connect(mongoData.ConString, { useNewUrlParser: true },
function(err, db) {
if (err) throw err;
var myDB = db.db(dbName); // create DB for once
for (var key in mongoData.Customers) {
//make call to the function and wait for the response
existsInDatabase(mongoData.Customers[key], 'Customers', function(err, result) {
//once the response came excute the next step
if (result) {
myDB.collection('Customers').insertOne(mongoData.Customers[key], function(err, res) {
if (err) throw err;
console.log('1 document inserted');
});
}
});
}
for (var key in mongoData.Categorys) {
//make call to the function and wait for the response
existsInDatabase(mongoData.Customers[key], 'Customers', function(err, result) {
//once the response came excute the next step
if (result) {
myDB.collection('Categorys').insertOne(mongoData.Categorys[key], function(err, res) {
if (err) throw err;
console.log('1 document inserted');
});
}
});
}
// Both the for loop will work randomly without any order
function existsInDatabase(obj, collection, cb) {
var result = false;
myDB.collection(collection).findOne({ id: obj.id }, function(err, result)
{
if (err) {
//this cb will work only when db operation is complited
cb(err);
} else if (result) {
cb(null, true);
} else {
cb(null, false);
}
});
}
});
This code may result in some error. Feel free to ask more questions over it
db.db(dbName).collection(collection).find({}) returns a cursor per the docs. You are missing .toArray():
db.db(dbName).collection(collection).find({}).toArray()...

Mongoose Model#save return value

I would like to get the whole document instead of the added item when I do a save().
var newTodo = Todos({
ID: req.body.ID,
RuleName: req.body.RuleName
});
newTodo.save(function (err, todos) {
if (err) throw err;
res.send(todos);
});
You cannot get it, unless you extend model method, or get it inside save
Simple version
newTodo.save(function (err, todos) {
if (err) throw err;
Todos.find(err, todos) {
if (err) throw err;
res.send(todos);
}
});
Version with custom method
// in schema definition
TodosSchema.methods.saveAndFind = function(cb) {
var self = this;
self.save(function(err) {
if(err) throw err;
return self.model('Todos').find({}, cb);
})
};
// in controller
var newTodo = Todos({
ID: req.body.ID,
RuleName: req.body.RuleName
});
newTodo.saveAndFind(function (err, todos) {
if (err) throw err;
res.send(todos);
});

Nodejs Angularjs Mongoose Query inside async map

From the codes below, I can add to my database. However, when I am trying to mongoose-find to look for the database, I am not getting any value. can anybody help? I want to res.json the result.
app.post('/api/infosave', function(req,res){
async.series([function (cb){
dbinfo.remove({}, function(err,result){
if (err) throw err;
cb(null);
});
}, function (cb){
var bookNum = [];
for (var i = 0; i < req.body.numBooks; i++) {
bookNum.push(i+1)
}
async.map(bookNum, function(num, cb) {
dbinfo.create({
numBooks: num,
done: false
}, cb);
}, cb);
}, function (cb){
dbinfo.find({},function(err,result){
if (err)
res.send(err);
res.json(result);
console.log(result);
cb(null);
});
}], function(error, results) {
});
});
As I said in my comment: you're calling res.json() inside one of the series function, and that won't work. What you should do is pass the result of dbinfo.find() to the local callback:
dbinfo.find({},function(err,result){
if (err)
cb(err);
cb(null, result);
});
, and in the async.series callback, call res.json():
...
}], function(error, results) {
if (error) return res.json({ error: error });
return res.json(results[2]); // the result you want should be third element of results
});

Node.js : res.redirect("back") got Error('Can\'t set headers after they are sent.');

I have a little problem on an express.js app.
Can't redirect back after several actions with callbacks.
My applications works with objects called "markers" and they are rated by "sub categories"
The goal of these actions is to merge 2 or more subcategories, move all markers from old to the new subcategoy, and finally, delete the old sub category.
Here is the code :
The action called after checked 2 or more subcategories :
exports.postMergeSubCategories = function(req, res) {
"use strict";
var data = {};
data.subCategories = req.body.subCategoriesChecked;
data.enName = req.body.subCategory.enName;
data.frName = req.body.subCategory.frName;
data.deName = req.body.subCategory.deName;
if (data.subCategories.length > 1) {
sscategoryMapper.merge(data, function(err, sscategory) {
if (err) return console.log(err);
console.log ('Sub categories merged !');
req.flash('mergeMessage', 'Merge completed!');
res.redirect("back");
});
} else { // error
console.log('Error while merge sub categories. It seems the number of sub categories checked less 2');
req.flash('mergeMessage', 'An error occured whil merge');
res.redirect("back");
}
};
The function sscategoryMapper.merge :
module.exports.merge = function(data, callback) {
async.waterfall([
function(callback){ // create new sub category
save(data, function(err, sscategory) {
if (err) return callback(err);
callback(null, sscategory._id);
});
},
function(sscategoryId, callback){ // update marker2sscategory
async.each(data.subCategories.split('|'), function(oldSscategoryId, err) { // for each subcategories, update markers2sscategory and remove the old subcategory
async.waterfall([
function(callback) { // update maker2sscategory to set the new sscategoryId to all target markers
marker2sscategoryMapper.update(sscategoryId, oldSscategoryId, function(err) {
if (err) return callback(err);
callback(null, oldSscategoryId);
});
},
function(oldSscategoryId, callback) { // delete the old sscategory
remove(oldSscategoryId, function(err) {
if (err) return callback(err);
callback();
});
}
], function(err) {
if (err) return callback(err);
callback();
});
}, function(err) {
callback();
});
callback(null, sscategoryId);
}
], function (err, result) {
if (err) return callback(err);
callback(null, result);
});
};
UPDATE
First problem here : I called 2 times the callback ... This piece of code moved to that :
}, function(err) {
callback(null, sscategoryId);
});
}
], function (err, result) {
if (err) return callback(err);
callback(null, result);
});
};
UPDATE END
The function marker2sscategoryMapper.update :
module.exports.update = function(to, from, callback) {
// update marker2sscategory with the new subcategory
dbMarker2sscategory.update({'_sscategory' : from}, {
'_sscategory' : to
}, {multi: true}, function(err) {
if (err) return callback(new Error(err));
callback(null, to);
});
}
And the function remove :
var remove = module.exports.remove = function(id, callback) {
dbSscategory.remove({'_id' : id}, function(err) {
if (err) return callback(new Error(err));
callback(null, id);
});
};
If I comment "res.redirect("back")", it will works.
Else, the error is : "Error('Can\'t set headers after they are sent.');" 2 times.
I read that one of the causes can be duplicate callbacks ... But I don't see that in my code.
Appreciate your help.

Rewrite callback function with async series

I had this function:
exports.profileImage = function(req, res) {
var id = req.params.user_id;
userProvider.get(id, function(err, user){
if (err) throw err;
userProvider.getImageById(user['image_id'], function(err, image) {
if (err) throw err;
userProvider.writeImageToDisk(image, function(err, path){
if (err) throw err;
res.sendfile(path);
});
});
});
};
I rewrite it using local variables:
exports.profileImage = function(req, res) {
var id = req.params.user_id;
var userTemp = undefined;
var imageTemp = undefined;
async.series([
userProvider.get(id, function(err, user){
if (err) throw err;
userTemp = user;
}),
userProvider.getImageById(userTemp['image_id'], function(err, image) {
if (err) throw err;
imageTemp = image;
}),
userProvider.writeImageToDisk(imageTemp, function(err, path){
if (err) throw err;
res.sendfile(path);
})
]);}
I have a parameters which userProvider.getImageById (user json object) needs , it comes from userProvider.get function, Which invoked before, I save it to local variable.
I chose async to skip callback hell but it is not working.
Error:
Cannot read property 'image_id' of undefined.
userTemp is undefined at the point where you try to look up its image_id. It will only become not undefined when the previous asynchronous function has been called.
Using async.waterfall, you should be able to simplify your code considerably to something like (the untested);
var id = req.params.user_id;
async.waterfall([
function(callback) {
userProvider.get(id, callback);
},
function(user, callback) {
userProvider.getImageById(user['image_id'], callback);
},
userProvider.writeImageToDisk,
res.sendfile
]);
That's not how async.series works.
Each function in the array has to have the following signature:
function(callback) { ...; callback(...); };
Once the code for each function is done, you need to call the callback function to signal to async that it should run the next step (docs).
So your code should look like this:
async.series([
function(callback) {
userProvider.get(id, function(err, user) {
if (err) throw err;
userTemp = user;
callback();
});
},
function(callback) {
userProvider.getImageById(userTemp['image_id'], function(err, image) {
if (err) throw err;
imageTemp = image;
callback();
});
},
function(callback) {
userProvider.writeImageToDisk(imageTemp, function(err, path){
if (err) throw err;
res.sendfile(path);
callback();
});
}
]);
Better yet, take a look at async.waterfall for a way of passing results from one async function to the next.

Resources