I am new to NodeJS, but a pretty experienced programmer. I am working on a side project to get my feet wet with node. I am looking at MongoDB for storage. To get started I just wanted to test creating some basic CRUD functions in node. I have included my code below.
My question is regarding the global var 'updated'. It is used in the 'read' and 'update' functions to control which function gets called next. I would like to call them in the following order: create, read, update, read, delete.
It is my understanding that I cannot pass in a function parameter to 'read' to dictate which function to call next because this is called asynchronously and the lambda function I am defining as my callback to mongojs.read will not have the parent functions parameters to use.
What is the correct way to do this? Using a global feels like a hack. :)
// app.js
//<user>:<password>#<server>/<database>
var connection = "foo:bar#127.0.0.1/blah";
var collections = ["scores"]
var db = require("mongojs").connect(connection, collections);
//console.log(db);
var updated = false;
function my_create()
{
console.log("my_create");
db.leaderboard.save({name: "sunder", score: 42}, function(err, result) {
if(err || !result )
{
console.log("Score save failed");
console.log(err);
}
else
{
console.log("Score save successful");
my_read();
}
});
}
function my_read()
{
console.log("my_read");
// app.js
db.leaderboard.find({name: "sunder"}, function(err, results) {
if(err || !results)
{
console.log("Score read failed");
console.log(err);
}
else
{
results.forEach(function(score)
{
console.log(score);
});
// I am sure using globals is a HACK. Need to figure this out.
if(updated)
my_delete();
else
my_update();
}
});
}
function my_update()
{
console.log("my_update");
db.leaderboard.update({name: "sunder"}, {$set: {score: 1337}}, function(err, result) {
if(err || !result)
{
console.log("Score update failed");
}
else
{
console.log("Score update successful");
updated = true;
my_read();
}
});
}
function my_delete()
{
console.log("my_delete");
db.leaderboard.remove({name: "sunder"}, function(err, result) {
if(err || !result)
{
console.log("Score delete failed");
}
else
{
console.log("Score delete successful");
process.exit();
}
});
}
// kick off the flow of processes
my_create();
How about passing the next action to perform into the my_read() function like this:
// app.js
var connection = "foo:bar#127.0.0.1/blah";
var collections = ["scores"]
var db = require("mongojs").connect(connection, collections);
function my_create()
{
console.log("my_create");
db.leaderboard.save({name: "sunder", score: 42}, function(err, result) {
if(err || !result )
{
console.log("Score save failed");
console.log(err);
}
else
{
console.log("Score save successful");
my_read("update");
}
});
}
function my_read(next_action)
{
console.log("my_read");
// app.js
db.leaderboard.find({name: "sunder"}, function(err, results) {
if(err || !results)
{
console.log("Score read failed");
console.log(err);
}
else
{
results.forEach(function(score)
{
console.log(score);
});
// I am sure using globals is a HACK. Need to figure this out.
if(next_action === "delete")
my_delete();
else
my_update();
}
});
}
function my_update()
{
console.log("my_update");
db.leaderboard.update({name: "sunder"}, {$set: {score: 1337}}, function(err, result) {
if(err || !result)
{
console.log("Score update failed");
}
else
{
console.log("Score update successful");
updated = true;
my_read("delete");
}
});
}
function my_delete()
{
console.log("my_delete");
db.leaderboard.remove({name: "sunder"}, function(err, result) {
if(err || !result)
{
console.log("Score delete failed");
}
else
{
console.log("Score delete successful");
process.exit();
}
});
}
// kick off the flow of processes
my_create();
Related
calling id from mongodb with callback function
var GetID = function (nameval, callback) {
console.log(nameval);
console.log("munesh hello");
GenerateID.find({ "id_name": nameval }, {
"id_code": 1,
"id_value": 1, "_id": 0
}, function (err, genvalue) {
if (err) {
console.log('hello');
}
else {
if (genvalue === null) {
callback(err, false);
}
else {
callback(err, true);
}
}
console.log(genvalue);
});
};
and calling above method so we need
so we need id from GenerateID.GetID and do our own work.
var region_id = GenerateID.GetID(name, function (error, result) {
if (error) {
console.log("getting any error");
} else {
console.log(region_id);
if (!result) {
console.log('data is not coming');
} else {
console.log('data is coming');
}
}
});
You have a number of issues. In the first piece of code, you need to pass the actual value when calling the callback.
In the second, you need to set region_id = result.
Ideally you would do this using promises as demonstrated below.
var GetID = function(nameval){
return new Promise((resolve,reject) => {
console.log(nameval);
console.log("munesh hello");
GenerateId.find({ "id_name" : nameval },{"id_code":1 , "id_value":1, "_id":0},
function( err , genvalue ) {
console.log(genvalue);
if (err) {
console.log('hello');
return reject()
}
if (genvalue == null) { return resolve(false); }
return resolve(genValue);
});
});
}
var GetIDPromise = GenerateId.GetID(name);
GetIDPromise.then(
genValue => {
if ( genValue == false ){
console.log('data is not coming');
// handle region id not being available. Perhaps return and show an error
}else{
var region_id = genValue;
// continue execution and use region id.
}
},
error => {
console.log("getting any error");
}
)
the following function creates new folder on my server via xmlrpc
var createFolder = function(folder_name) {
var defer = Q.defer();
client.methodCall('create_folder', [sessionID, folder_name], function(err, resp) {
if (err) {
if (err.responseString && err.responseString.match('already exist')) {
//call the same function recursively with folder_name+Math.round(Math.random()*100)
} else {
defer.reject(err);
}
} else {
defer.resolve(folder_name);
}
});
return defer.promise;
}
The functions creates a new folder successfully
However, if folder already exists i want to fire this function again recursively with new folder name and then return it in promise so that whenever this function is called it'll return the folder name doesn't matter how many times it was executed
something like
createFolder('directory').then(function(resp){
console.log(resp);// may return directory || directory1 .... etc
});
**EDIT **
so i manged to achieve this by passing the defer object
let me know if there are more elegant ways of achieving this
var createFolder = function(folder_name,defer) {
defer =defer || Q.defer();
client.methodCall('create_folder', [sessionID, folder_name], function(err, resp) {
if (err) {
if (err.responseString && err.responseString.match('already exist')) {
return createFolder(folder_name+Math.round(Math.random()*100,defer)
} else {
defer.reject(err);
}
} else {
defer.resolve(folder_name);
}
});
return defer.promise;
}
Never do any logic in plain (non-promise) callbacks. Promisify at the lowest level:
var defer = Q.defer();
client.methodCall('create_folder', [sessionID, folder_name], function(err, resp) {
if (err) defer.reject(err);
else defer.resolve(folder_name);
});
return defer.promise;
Or much simpler with Q.ninvoke:
return Q.ninvoke(client, 'methodCall', 'create_folder', [sessionID, folder_name]);
Now we can start implementing our recursion. It's quite simple with a then callback, from which you can return another promise. In your case:
function createFolder(folder_name) {
return Q.ninvoke(client, 'methodCall', 'create_folder', [sessionID, folder_name])
.catch(function(err) {
if (err.responseString && err.responseString.match('already exist')) {
return createFolder(folder_name+Math.floor(Math.random()*100));
} else {
throw err;
}
});
}
Here is a bad simple way of solving your problem:
var createFolder = function(folder_name) {
var defer = Q.defer();
client.methodCall('create_folder', [sessionID, folder_name], function(err, resp) {
if (err) {
if (err.responseString && err.responseString.match('already exist')) {
//call the same function recursively with folder_name+Math.round(Math.random()*100)
defer.resolve(createFolder(folder_name+Math.round(Math.random()*100)));
} else {
defer.reject(err);
}
} else {
defer.resolve(folder_name);
}
});
return defer.promise;
}
However, defer is considered bad practice. Here is a very nice article about promises.
You should favor something like:
var createFolder = function(folder_name) {
return Q.Promise(function(resolve, reject){
client.methodCall('create_folder', [sessionID, folder_name], function(err, resp) {
if (err) {
if (err.responseString && err.responseString.match('already exist')) {
//call the same function recursively with folder_name+Math.round(Math.random()*100)
resolve(createFolder(folder_name+Math.round(Math.random()*100)));
} else {
reject(err);
}
} else {
resolve(folder_name);
}
});
});
}
EDIT: as noted by #Bergi, this is still not right and hard to debug. Any potential errors thrown from the callback of methodCall won't actually reject the promise and will most likely be swallowed (even though this callback seems very little error-prone, it might evolve). Please refer to his answer for a better way of doing this.
Also, see the official Q doc here.
I anticipate a callback hell is beginning to form in my code so I decided to start using promises. But I can't wrap my head around implementing it. For example I have a function:
DB.prototype = {
findUser: function (username) {
this._pool.getConnection(function (err, connection) {
if (err) { return callback(true, false); }
connection.query('SELECT password_hash, password_salt FROM users WHERE email = ? AND admin = 1', username,
function (err, rows) {
connection.release();
if (rows.length === 1) { callback(false, rows[0]); }
else { callback(false, false); }
});
connection.on('error', function () { callback(true, false); });
});
}
};
How would I adapt this to using promises instead of callbacks? And how would I use this adapted db.findUser() ?
EDIT:
I got something working. It looks like this:
DB.prototype = {
getConnection: function() {
return this._pool.getConnectionAsync();
}
}
And the usage:
Promise.using(db.getConnection(), function(connection) {
return connection.queryAsync("SELECT password_hash, password_salt FROM users WHERE email = ? AND admin = 1", "exampleUser")
.then(function(rows) {
connection.release();
console.log("is there a row?", rows.length === 1, rows);
// do something with results
});
}).catch(function(err) {
// This is only run if an error is thrown
console.log("error is", err);
});
Is this a good implementation or could something be improved?
With this as a URL:
'api/support-tag/name/myTagName'
This function works properly:
getByName: function (req, res) {
model.Shared_SupportTag.findOne({name: req.params.name}).exec(function (err, results) {
if (err) {
return res.status(400).send({
message: errMsg.Util_ErrorMsg.getErrorMessage(err)
});
}
res.send(results);
})
}
But when I try to call a similar function from within the node server:
supportDoc.category = GetById(item.category);
function GetById(name){
model.Shared_SupportTag.findOne({name: name}).exec(function(err, result){
if(err){
console.log(err)
}else{
console.log(result);
}
})
}
The function does not execute, nor does the error catch, intellisense shows:
err= Reference error; err is not defined
result = Reference error; result is not defined
All I am trying to accomplish is a function call from within the server and not via a URL.
Any solution here? Thanks in advance
In the case of the findOne() method, the positive response (sans error) will either hold a mongoose object or null.
If the same query had been sent using just find(), the result would have been an empty array.
function GetById(name){
model.Shared_SupportTag.findOne({name: name}).exec(function(err, result){
if(err){
console.log(err)
}else{
if (result) console.log(result); //Check whether object exists.
else console.log('Not found!');
}
})
}
Solved:
model.Shared_SupportDoc.find({}).exec(function (err, collection) {
var supportDocs = require('../../data/_seed/support/supportDocs.json');
if (collection.length === 0) {
supportDocs.forEach(function (item) {
var supportDoc = new model.Shared_SupportDoc;
supportDoc.title = item.title;
supportDoc.created = item.date;
supportDoc.icon = item.icon;
supportDoc.likeCount = item.likeCount || 7;
-----> // requires callback - ie asynchronous
GetByName(item.category, function(tagId) {
supportDoc.categoryId = tagId;
-----> // must put save in the callback
supportDoc.save(function (err) {
if (err) {
console.log(supportDoc.categoryId)
console.log('Error: ' + err);
} else {
console.log('Support Doc Seed Complete');
}
});
});
})
}
});}
function GetByName(name, next) {
model.Shared_SupportTag.findOne({name : name}).exec(function (err, result) {
if (!result) {
console.log('Not Found');
next();
} else {
console.log(result._id);
next(result._id);
}
});}
I am using MongooseJS to connect to MongoDB
I am creating a a new Team. Once I save the team, I want to add a reference to the team for my user. But unfortunately, whenever I go to add the team to my user and save the user my callback is not called. Any ideas why?
TeamRepository.prototype.createForUser = function(user, data, callback) {
var team,
_this = this;
if (user == null) {
user = null;
}
if (data == null) {
data = {};
}
if (callback == null) {
callback = (function() {});
}
team = new Team({
name: data.name,
description: data.description,
state: data.state
});
return this.save(team, function(err) {
if (err) {
return callback(err, team);
} else {
user.teams.push({
team: team._id
});
return _this.save(user, function(err) {
return callback(err, team);
});
}
});
};
Specifically this line. Notice I have two nested saves.
return _this.save(user, function(err) {
return callback(err, team);
});
Any help would be great.
I have solved the issue. In my user model, I had hooked into the save event and was not calling next() to move along the save.
schema.pre('save', function(next) {
var _this = this;
if (this.isNew || this.isModified('password')) {
return hashPassword(this.password, function(err, hash) {
if (err) {
return next(err);
}
_this.password = hash;
return next();
});
} else {
return next(); // missing this
}
});
And here is what my TeamRepository.save() looks like since you asked.
TeamRepository.prototype.save = function(model, callback) {
if (model == null) {
model = null;
}
if (callback == null) {
callback = (function() {});
}
if (model) {
model.increment();
return model.save(callback);
} else {
if (callback) {
return callback(null);
}
}
};