NodeJS How to organize Callbacks in a controller - javascript

I am new to NodeJS and coming from a PHP environment I am trying to figure out how to work with multiple callbacks. I do understand the basics about callback and I think it does make sens when writing modules. My problem is when comes the time to use those modules how to organize all the callbacks. Below is my implementation a request reset password controller method (I am using SailsJS). This is a first draft of my code. It was mainly to test a method of organizing callbacks. What do you guys think of this structure ? Is there a better way do it?
var _ = require('lodash');
var moment = require('moment');
var mailer = require("../../services/Mailer");
var crypto = require('../../services/crypto');
var forms = require("forms"),
fields = forms.fields,
validators = forms.validators;
module.exports = {
// Request reset user password: submit form and send email
request_process : function(req, res, next) {
var form = createForm();
form.handle(req, {
// there is a request and the form is valid
// form.data contains the submitted data
success: function (form) {
var user = null;
var username = form.data.username;
User.findOne({'username' : username}, foundUser);
function foundUser( err, _user){
if(err)
res.send(500, 'User not found');
user = _user;
if user.isPasswordRequestNonExpired()
return res.view('user/resetting/passwordAlreadyRequested');
if !user.passwordRequestToken
user.passwordRequestToken = crypto.randomToken();
renderEmail(null, user);
}
function renderEmail(err, user){
res.render('email/resetting_check_email', {'user': user, }, sendEmail );
}
function sendEmail(err, template){
if(err)
return res.send(500, "Problem with sending email");
Mailer.send( user, "Reset Password", template, sentEmail);
}
function sentEmail(err, response){
if(err)
return res.send(500, "Error sending email");
user.passwordRequestedAt = moment().format();
user.save(finish);
}
function finish(err){
if(err)
return res.send(500);
res.view();
}
},
// the data in the request didn't validate,
// calling form.toHTML() again will render the error messages
error: function (form) {
console.log("registration error", form);
res.locals.form = form;
return res.render('user/registration/register');
},
// there was no form data in the request
empty: function (form) {
console.log("registration empty", form);
res.locals.form = form;
return res.render('user/registration/register');
}
},
// Tell the user to check his email provider
check_email : function(req, res, next) {
// if empty req.params.email
// redirect request view
// res.view('check_email.ejs')
},
// Reset user password
reset : function(req, res, next){
// find userByPasswordToken
// if !user
// res.view ('invalid or expired "confirmation token".')
// user.update password
// res.view('reset.ejs');
},

Node.js callback basics:
Most of the functions (Node and its Libraries (called modules)), are of asynchronous (async) nature.
These functions have a common signature with callback as the last argument: function(arguments.....callback).
The callback is just another JavaScript function. ( yes, in Javascript, functions can be passed around as arguments to other functions). Node.js typical callbacks have a signature with first argument as error (if one happened): callback(err,outputs......).
example: first argument is a string, second an object (defined inline) and the last is a function (defined inline).
doSomeWork('running',{doFast:true,repeat:20}, function(err,result){
if(err){
console.log('ohnoes!);
} else {
console.log('all done : %s',result);
}
});
which is equivalent to:
var type = 'running';
var options = {doFast:true,repeat:20};
var callback = function(err,result){
if(err){
console.log('ohnoes!);
} else {
console.log('all done : %s',result);
}
};
doSomeWork(type,options,callback);
So the basic contract here is give a function its arguments and pass a callback to be invoked, when it is done. The passed call back will be invoked some where in future when there is something to return, error or the results.
Multiple nested callbacks are generally less readable and complex:
function uploadAll(path,callback){
listFiles(path,function(err,files){
if(err){
callback(err);
}else{
var uploaded = [];
var error;
for(var i = 0 ; i < files.length; i++){
uploadFile(files[i],function(err,url){
if(err){
error = err;
break;
}else{
uploaded.push(url);
}
});
}
callback(error,uploaded);
}
});
};
But fortunately there are modules like async that help organize callbacks:
function uploadAll(path,callback){
async.waterfall(
[
function(cb){
listFiles(path,cb);
},
function(files,cb){
async.map(files,uploadFile,cb);
}
],callback);
}
Furthermore, there is a Promises pattern as well. Future versions support generators which provide many new async patterns.

you can use async or q to manage the callback pyramids

Related

How to wait until mysql query is done and then continue to run the script?

I've made a class in which the constructor is being declared mostly by a mySQL query that looks like that:
constructor(username) {
this.mysql = require('mysql');
// create a connection variable with the required details
this.con = this.mysql.createConnection({
host: "localhost", // ip address of server running mysql
user: "root", // user name to your mysql database
password: "", // corresponding password
database: "db" // use the specified database
});
this.username = username;
this._password = "";
this.con.query("SELECT * FROM users WHERE username = ?", this.username, function (err, result, fields) {
if (err) throw err;
this._password = result[0].password;
});
}
get password() {
return this._password;
}
The issue is , when I'm declaring the class like that:
const user= require("./user.js");
let bot = new user("user1");
console.log(user.password();
The code first accessing the get and getting undefined , and only after the the query is done.
How can I fix it?
You can't make a constructor asynchronous, that's not supported by Javascript.
What you can do is create an asynchronous createUser function that returns a user, and modify the constructor of your User class to take the data returned from the database.
Something like:
class User {
// Synchronous constructor
constructor(mysqlData) {
this._password = mysqlData.password;
}
}
// Asynchronous create user function
function createUser(name, callback) {
con.query('your query', [], function (err, result) {
if (err) return callback(err); // Traditional error-first callback
const user = new User(result[0]);
callback(null, user);
}
}
// Create the user and then do something once the user is created
createUser('user1', function (err, user) {
if (err) throw err;
console.log(user.password());
});
Unrelated tip: look into Promises and async/await, they are so much nicer to use than callbacks. Then your createUser function would look something like this (notice the await keyword instead of a callback for the query):
async function createUser(name) {
const result = await con.query('your query', []);
const user = new User(result[0]);
return user;
}
And then you can do const user = await createUser('user1'); from another async function (you can only use await inside of an async function).
Keep in mind that for this to work, you need:
A runtime that supports async/await (Node 7.6+ or a modern browser), or Babel to transpile the code so it can run in older runtimes & browsers.
A mysql client that supports promises (there are wrappers on npm for most packages).

callback is not a function in node js

I am trying a simple operation that will result the particular users details from the database. Pooling database and all other connections work perfectly but the callback is not working. Am I doing anything wrong here?
Below is the code I use.
db_crud.js
var express = require('express');
var app = express();
var crud = require('./routes/crud_op_new.js');
var search = require('./routes/search.js');
var connection;
var result;
app.get('/search',(req,res)=>{
crud.connection(function (con) {
search.getuser(con,req.param('name'),result);
res.send(result);
});
});
app.listen(8003);
Finally here is where the error occurs ... search.js
exports.getuser = function(connection,req,callback){
console.log("GET Request iniciated");
connection.query("select * from user,addr where name=? and user.id=addr.e_id",[req],(err,row)=>{
if(err){
callback("DB ERROR: "+err);
}
else {
if(row.length==0){
callback("No Records found");
}
else {
callback(row);
}
}
});
}
The db_crud will send the credentials to search.js and here the callback is called to send result. crud_op_new.js creates the db pool connection and is in variable con.
As mentioned by Jaromanda X in the answer, result is just declared and unassigned which should be a callback function.
Also, the callback in search.js is being returned the error and result both as the first argument. You have to change this callback(row) to callback(null, row) to handle the error and result as below.
Note: Best practice in node js callback function would be, first argument
should return an error (null in case of no error) and then remaining arguments can be the return
values.
db_crud.js
var express = require('express');
var app = express();
var crud = require('./routes/crud_op_new.js');
var search = require('./routes/search.js');
var connection;
app.get('/search',(req,res)=>{
crud.connection(function (con) {
search.getuser(con,req.param('name'), function(err, result) {
if(err) {
res.status(501).send(err);
} else {
res.send(result);
}
});
});
});
app.listen(8003);
search.js
exports.getuser = function(connection,req,callback){
console.log("GET Request iniciated");
connection.query("select * from user,addr where name=? and user.id=addr.e_id",[req],(err,row)=>{
if(err){
callback("DB ERROR: "+err);
}
else {
if(row.length==0){
callback("No Records found");
}
else {
callback(null, row);
}
}
});
}
you call your function search.getuser(con,req.param('name'),result); ... result is not a function, it's undefined ... a callback needs to be a function so it can be called back
This should work
app.get('/search',(req,res)=>{
crud.connection(function (con) {
// vvvvvvvvvvvvvvv this is the callback function
search.getuser(con,req.param('name'),function(result) {
res.send(result);
});
});
});

NodeJS is asynchronous and my code doesn't run in the order I am expecting

postRegistrationHandler: function (account, req, res, next) {
console.log('postRegistrationHandler activated');
account.getCustomData(function(err, data) {
if (err) {
console.log(err.toString, "error string");
return next(err);
} else {
data.mongo_id = userCreationCtrl(account);
data.save();
next();
}
});
},
This function almost works properly, but the line:
data.save();
runs before the previous line finishes which means that the data I want to save isn't present at the appropriate time.
data.mongo_id = userCreationCtrl(account);
This line calls a function that creates a mongoDB document with information in the account object and then returns the _id (which is what I am trying to save.
I thought maybe using a .then() would help but that seems to be unavailable here for some reason. If anyone sees something I'm missing, that would be quite helpful. Thank you!
Here is the userCreationCtrl file as requested:
var UserSchema = require('./../models/UserModel.js');
var createNewUser = function (account, res, next){
// We will return mongoId after it is created by submitting a newUser
var mongoId = "";
// Save StormpathID (last 22 characters of account.href property)
var newStormpathId = account.href.slice(account.href.length - 22);
console.log('stormpath ID:', newStormpathId, 'just registered!');
console.log(account);
// Create new user from model by recycling info from the Stormpath registration form and include the stormpathId as well.
var newUser = new UserSchema({
stormpathId: newStormpathId,
firstName: account.givenName,
lastName: account.surname,
email: account.email,
street: account.street,
city: account.city,
zip: account.zip
});
// This saves the user we just created in MongoDB
newUser.save(function(err, result){
console.log(result);
if (err) {
console.error(err);
}
else {
console.log("User created in MongoDB, attempting to return mongoDB _id to stormpath customData");
// Keep track of the new user's mongo _id so we can return it to the previous function and save it as Stormpath custom data.
mongoId = result._id;
console.log(mongoId, "mongoid");
return result._id;
}
});
};
module.exports = createNewUser;
You have userCreationCtrl expecting 3 arguments, account, res, and next. next is the callback that should be called after the user is created so instead of return result._id you should call next like so:
// inside of createNewUser()
newUser.save(function(err, result){
console.log(result);
if (err) {
console.error(err);
}
else {
console.log("User created in MongoDB, attempting to return mongoDB _id to stormpath customData");
// Keep track of the new user's mongo _id so we can return it to the previous function and save it as Stormpath custom data.
mongoId = result._id;
console.log(mongoId, "mongoid");
// IMPORTANT change to make it all work...
// get rid of return result._id because its not doing anything
// pass the value to your callback function instead of returning the value
next(null, result._id);
}
});
then calling code in postRegistrationHandler should look like this:
account.getCustomData(function(err, data) {
if (err) {
console.log(err.toString, "error string");
return next(err);
} else {
// pass in a callback as the 3rd parameter that will be called by newUser.save() when its finished
userCreationCtrl(account, null, function(err, resultId) {
data.save();
next();
});
}
});

node.js - express - res.render() : Correct format for feeding variables into the JSON parameter?

I'm learning node.js so bear with me.
I'm trying to create a node.js web application using express+jade that is basically just a line queue. (I.E. take a number, wait in line, now serving number 4...except the 4 will be a mysql table field). The page will auto-update every 5 seconds. There are three line queues handled by the page (I.E) :3000/1 :3000/2 :3000/3.
To be clear, I have the application working, but I want to make sure I am doing it correctly as opposed to just hacking it together with poor methodology.
In my index.js I have the standard setup:
exports.bio = function(req, res){
res.render('index', {
location: 'Biometrics',
number: bio()
});
};
exports.interview = function(req, res){
res.render('index', {
location: 'Interview',
number: interview()
});
};
exports.docs = function(req, res){
res.render('index', {
location: 'Documentation',
number: doc()
});
};
I am currently also calling the values for the "number:" JSON value from within the index.js as well.
var doc = (function() {
//do javascript and data calls
return a;
});
var bio = (function() {
//do javascript and data calls
return a;
});
var interview = (function() {
//do javascript and data calls
return a;
});
My question is: What would be the recommended way to do this or am I on the right track?
This will work as long as the functions doc(), bio(), and interview() are synchronous, but most likely that won't be the case, particularly if they need to perform some database access.
If these functions were async then your could should look like this:
exports.docs = function(req, res){
// call the doc() function and render the response in the callback
doc(function(err, number) {
res.render('index', {
location: 'Documentation',
number: number
});
});
};
The doc() function will look like this:
var doc = (function(callback) {
// This code very likely be async
// therefore it won't return any value via "return"
// but rather calling your function (callback)
db.doSomething(someparams, callback);
});
Inside db.doSomething() there will be a call to your function callback(err, theValue)
The asynchonous way would be something like:
exports.docs = function(req, res) {
fetchSomeValueFromMysql(req.param('foo'), function (err, data) {
if (err) {
res.send(500, 'boom!');
return;
}
res.render('index', {
location: 'Documentation',
number: data
});
});
};
Say if you had an async operation in bio();
exports.bio = function (req, res) {
bio(function (err, data) {
if (!err && data) {
res.render('index', {
location: 'Biometrics',
number: data
});
} else {
// handle error or no data
res.render('error');
}
});
}
var bio = function(cb) {
//do javascript and data calls
cb(err, data);
});
Again, there are many ways to get this working. But the above should do.

Cannot return values to response with mongoose/mongodb and nodejs

I am using Nodejs, ExpressJs, MongoDB via Mongoose. I have created a simple UserSchema . I have my code separated into multiple files because I foresee them getting complex.
The url '/api/users' is configured to call the list function in 'routes/user.js' which happens as expected. The list function of UserSchema does get called, but it fails to return anything to the calling function and hence no result goes out.
What am I doing wrong ?
I tried to model it based on http://pixelhandler.com/blog/2012/02/09/develop-a-restful-api-using-node-js-with-express-and-mongoose/
I think I am doing something wrong with the function definition of userSchema.statics.list
app.js
users_module = require('./custom_modules/users.js'); // I have separated the actual DB code into another file
mongoose.connect('mongodb:// ******************');
var db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function callback() {
users_module.init_users();
});
app.get('/api/users', user.list);
custom_modules/users.js
function init_users() {
userSchema = mongoose.Schema({
usernamename: String,
hash: String,
});
userSchema.statics.list = function () {
this.find(function (err, users) {
if (!err) {
console.log("Got some data"); // this gets printed
return users; // the result remains the same if I replace this with return "hello"
} else {
return console.log(err);
}
});
}
UserModel = mongoose.model('User', userSchema);
} // end of init_users
exports.init_users = init_users;
routes/user.js
exports.list = function (req, res) {
UserModel.list(function (users) {
// this code never gets executed
console.log("Yay ");
return res.json(users);
});
}
Actually in your code you are passing a callback, which is never handled in function userSchema.statics.list
You can try the following code:
userSchema.statics.list = function (calbck) {
this.find(function (err, users) {
if (!err) {
calbck(null, users); // this is firing the call back and first parameter should be always error object (according to guidelines). Here no error, so pass null (we can't skip)
} else {
return calbck(err, null); //here no result. But error object. (Here second parameter is optional if skipped by default it will be undefined in callback function)
}
});
}
Accordingly, you should change the callback which is passed to this function. i.e.
exports.list = function (req, res){
UserModel.list(function(err, users) {
if(err) {return console.log(err);}
return res.json(users);
});
}

Categories

Resources