Execute my functions Asynchronously in NodeJS - javascript

I am new to NodeJS. I know there are a lot of questions about asynchronous NodeJS but I couldn't find exactly what I am looking for.
My problem is:
I want to check if username and email already exist or not in my database. Two separate functions for username and email. Another function is for storing data to database.
I don't know how to do this using asynchronous NodeJS pattern.
User.js (mongoose Schema)
const mongoose = require('mongoose');
var userSchema = mongoose.Schema({
name: String,
username: { type: String, required: true, unique: true },
password: { type: String, required: true },
email: { type: String, required: true, unique: true},
aiub_id: String,
});
const Users = module.exports = mongoose.model('User', userSchema);
module.exports.addUser = function (user, callback) {
user.save(callback);
}
module.exports.usernameExist = function (givenUsername, callback) {
Users.find({ username: givenUsername }, callback);
}
module.exports.emailExist = function (givenEmail, callback) {
Users.find({ username: givenEmail}, callback);
}
index.js (route)
route.post('/signup', function(req, res){
// GRAB USER INFO FROM HTML FORM
var newUser = new User({
name : req.body.tfullName,
username : req.body.tusername,
password : req.body.tpassword,
email : req.body.temail,
aiub_id : req.body.tuserID
});
// This block send 200 if username doesn't exist
User.usernameExist(newUser.username, function (err, result){
if(err){
throw err;
}
if(result.length <= 0){
res.send({status : 200 });
}else{
res.send({status : 100 });
}
});
});
Please me help to solve this and please forgive if it sounds stupid.

Use Promises.
First I suggest you change your functions to return promises instead of taking callbacks:
function userExists(parameters) {
return new Promise((resolve, reject) =>
Users.find(parameters, (err, result) => {
if(err){
reject(err);
} else {
resolve(result.length <= 0);
}
})
);
}
module.exports.usernameExist = function (givenUsername) {
return userExists({ username: givenUsername });
}
module.exports.emailExist = function (givenEmail) {
return userExists({ email: givenEmail });
}
Then you wrap the promises returned from parallell calls to these functions in a Promise.all, which returns a new promise that resolves when all the wrapped promises get resolved, and then you do your stuff there:
Promise.all([
User.usernameExist(newUser.username),
User.emailExist(newUser.email)
]).then((results) => {
// results[0] contains the result from User.usernameExist
// results[1] contains the result from User.emailExist
});
If you do not want to change your functions in the User module, you can wrap the calls to those functions in Promises in your index.js file instead.

Just call one function in callback of other,
// Check if username doesn't exist
User.usernameExist(newUser.username, function (err, result){
if(err){
throw err;
}
if(result.length <= 0){
//check if email doesn't exist
User.emailExist (newUser.email, function(err, result){
if(result.length<=0){
//save user
User.addUser(newUser, (err, result)=>{
if(!err){
res.send({status : 200 });
}else{
res.send({status : 100 });
}
})
}
}
}
});
People prefer promises to callback, to use promise you'll have to return a promise from your function. With promises code is easier to read so look into that.

Related

How could I refactor the waterfall method from async to use ES6 promises?

I have a route that allows a user to reset their password by sending them an email. Standard procedure for most websites. In this route, I import the async npm module and use the waterfall method so that I can handle the asynchronous nature of multiple functions. I'm still having a bit of trouble understanding promises, but I'm trying to replace waterfall with a promise or promise chain.
How could I refactor this route with a promise? Here are the steps contained in this route that is currently split up into 4 functions with waterfall.
First the route creates a reset token
Search for user based on email
2.5. If user is found, save user, otherwise return 404
Send email to user containing a reset url
Return a status of 200.
app.post('/forgotPassword', function(req, res, next) {
waterfall([
// generate reset token
function(done) {
crypto.randomBytes(20, function(err, buf) {
var token = buf.toString('hex');
done(err, token);
});
},
function(token, done) {
// search for user with the given email
User.findOne({ email: req.body.email }, function(err, user) {
// check to see if the user exists
if (!user) {
// user doesn't exist in database
return res.status(404).send();
}
// user exists, assign token with expiration date
user.resetPasswordToken = token;
user.resetPasswordExpires = Date.now() + 3600000; // 1 hour from now
// save the user model with the newly added
// token and expiration date
user.save(function(err) {
done(err, token, user);
});
});
},
function(token, user, done) {
var smtpTransport = nodemailer.createTransport('SMTP', {
service: 'SendGrid',
auth: {
user: config.sendgridUser,
pass: config.sendgridPassword
}
});
var mailOptions = {
to: user.email,
from: 'email#school.edu',
subject: 'Password Reset',
text: `Hello etc etc`,
smtpTransport.sendMail(mailOptions, function(err) {
done(err, 'done');
});
}],
function(err) {
// handle error
if (err) return next(err);
res.status(200).send();
});
}); // end POST route '/forgotPassword'
Promise is a very powerful tool. It can be hard to understand it at the beginning, but totally worth the effort! Please let me know if you have any doubts :)
app.post('/forgotPassword', function(req, res, next)
{
new Promise((resolve, reject) =>
{
// generate reset token
crypto.randomBytes(20, (err, buf) =>
{
if(err)
return reject(err);
const token = buf.toString('hex');
resolve(token);
});
})
.then((token) =>
{
return new Promise((resolve, reject) => {
// search for user with the given email
User.findOne({ email: req.body.email }, (err, user) =>
{
if (!user)
return reject(404);
// user exists, assign token with expiration date
user.resetPasswordToken = token;
user.resetPasswordExpires = Date.now() + 3600000; // 1 hour from now
// save the user model with the newly added
// token and expiration date
user.save(function(err) {
if(err)
return reject(err);
resolve(user);
});
});
});
})
.then((user) =>
{
return new Promise((resolve, reject) =>
{
const smtpTransport = nodemailer.createTransport('SMTP',
{
service: 'SendGrid',
auth: {
user: config.sendgridUser,
pass: config.sendgridPassword
}
});
const mailOptions = {
to: user.email,
from: 'email#school.edu',
subject: 'Password Reset',
text: `Hello etc etc`
};
smtpTransport.sendMail(mailOptions, (err) =>
{
if(err)
return reject(err);
resolve();
});
});
})
.then(() => res.sendStatus(200))
.catch((err) =>
{
//check if the error is the one from the DB where the user was not found
if(err == 404) {
return res.sendStatus(404);
}
return res.status(500).send(err);
});
});
bluebird is one of the most popular promise library.
and it offers promisify function to convert callback hell to promise.
please read this document and play with it.
http://bluebirdjs.com/docs/working-with-callbacks.html
This answer presumes 4 completely sequential steps:
(Function () {
Return Promise (function (resolve, promise) {
// try to do some stuff
if (success) {
Resolve ("pass this value on to next link in the chain");
}
Reject();
})()
.then (function (value) {
// do next step
Return "pass this value on to next link in the chain";
.then (function (value) {
// do next step
Return "pass this value on to next link in the chain";
.catch (function (error) {
// handle any reject or any error in the chain
}
You could in relation to the individual .then also choose to return a Promise. Beware: any errors in the .catch block will be swallowed.
This is just an example with ES6 promise but working code. You can refactor it further to have a clear reusable code. You can replace ES5 functions with ES6 arrow functions.
app.post('/forgotPassword', function(req, res, next) {
var catch = function(err){
return next(err);
}:
var error404 = function(){
return res.status(404).send();
};
var success = function(){
return res.status(200).send();
};
var step1 = new Promise(function(resolve, reject){
crypto.randomBytes(20, function(err, buf) {
if(err){
reject(err);
} else {
var token = buf.toString('hex');
resolve(token);
}
});
});
var step2 = function(token) {
// search for user with the given email
User.findOne({ email: req.body.email }, function(err, user) {
// check to see if the user exists
if (!user) {
// user doesn't exist in database
return {error404: true};
}
// user exists, assign token with expiration date
user.resetPasswordToken = token;
user.resetPasswordExpires = Date.now() + 3600000; // 1 hour from now
// save the user model with the newly added
// token and expiration date
user.save(function(err) {
return { error: err, token: token, user: user });
});
});
};
var step3 = function(obj) {
if(obj.error){
return catch(obj.error);
} else if(obj.error404) {
return error404();
} else {
var token = obj.token, user = obj.user;
var smtpTransport = nodemailer.createTransport('SMTP', {
service: 'SendGrid',
auth: {
user: config.sendgridUser,
pass: config.sendgridPassword
}
});
var mailOptions = {
to: user.email,
from: 'email#school.edu',
subject: 'Password Reset',
text: `Hello etc etc`,
smtpTransport.sendMail(mailOptions, function(err) {
if(err){
return catch(err);
} else {
return success();
}
});
}
};
step1.then(step2, catch).then(step3);
}); // end POST route '/forgotPassword'
Promises work best when you're dealing with a library where methods already return them, or a library like bluebird that lets you promisify existing callback APIs, otherwise it's a lot of conversions.
If you're still set on refactoring with plain es6, put your promise-wrappers at the lowest possible level, for best effect and cleanest error handling, basically promisify manually:
let convert = (resolve, reject) => (err, res) => err ? reject(err) : resolve(res);
crypto.randomBytesAsync = n=>new Promise((y,n)=>crypto.randomBytes(n,convert(y,n)));
User.findOneAsync = u => new Promise((y, n) => User.findOne(u, convert(y, n)));
User.prototype.saveAsync = () => new Promise((y, n) => this.save(convert(y, n)));
Then use them like this:
app.post('/forgotPassword', function(req, res, next) {
crypto.randomBytesAsync(20).then(buf => {
var token = buf.toString('hex');
// search for user with the given email
return User.findOneAsync({ email: req.body.email }).then(user => {
// check to see if the user exists
if (!user) {
// user doesn't exist in database
res.status(404).send();
throw;
}
// user exists, assign token with expiration date
user.resetPasswordToken = token;
user.resetPasswordExpires = Date.now() + 3600000; // 1 hour from now
// save the user model with the newly added
// token and expiration date
return user.saveAsync().then(() => {
var smtpTransport = nodemailer.createTransport('SMTP', {
service: 'SendGrid',
auth: { user: config.sendgridUser, pass: config.sendgridPassword }
});
smtpTransport.sendMailAsync =
o => new Promise((y, n) => smtpTransport.sendMail(o, convert(y, n)));
return smtpTransport.sendMailAsync({
to: user.email,
from: 'email#school.edu',
subject: 'Password Reset',
text: `Hello etc etc`,
});
});
})
})
.then(() => res.status(200).send())
.catch(err => next(err));
}); // end POST route '/forgotPassword'

sailsjs error handling when creating two models

I'm pretty new two nodejs and sails. I'm trying to create two models inside one action. My question is how to handle the errors which can occur in both queries.
Current Code:
new: function (req, res) {
var errorArray = [];
var data = req.allParams();
User.create({
username: data.username,
password: data.password,
device_id: data.device_id,
own_number: data.own_number,
mobile_numbers: data.mobile_numbers
}).exec(function (err, user) {
if(err){
errorArray.push(err);
}
});
Mobile_number.create({
number: data.own_number,
number_id: this.hash(data.own_number)
}).exec(function(err, mobile_number){
sails.log(err);
if(err){
errorArray.push(err);
}
});
if(errorArray.length == 0){
return res.ok('success');
}else {
return res.send(errorArray.toString());
}
}
The problem with this code is that the if at the end is handled before the queries finish. What would be the right way for to wait for the queries?
Bruno
First of all your code will not work because node.js is asynchronous. So you check if there are errors before functions are executed. Below are 2 solutions:
You can use async.series. If you use that it will stop executing if first method fails and it will return it's error. It it succeed it will go to second method.
async.series([
function(callback) {
User.create({
username: data.username,
password: data.password,
device_id: data.device_id,
own_number: data.own_number,
mobile_numbers: data.mobile_numbers
}).exec(callback);
},
function(callback) {
Mobile_number.create({
number: data.own_number,
number_id: this.hash(data.own_number)
}).exec(callback);
}
],
function(error, results) {
// error first finished
if(error)
res.send('error');
else
res.send('success');
}
);
Or you can do it traditional way with callbacks.
new: function(req, res) {
var errorArray = [];
var data = req.allParams();
var mobileCreateCallback = function(err, mobile_number, user) {
sails.log(err);
if (err) {
errorArray.push(err);
}
if (errorArray.length === 0) {
sails.log(user, mobile_number);
return res.ok('success');
} else {
return res.send(errorArray.toString());
}
};
var userCreateCallback = function(err, user) {
if (err) {
errorArray.push(err);
}
Mobile_number.create({
number: data.own_number,
number_id: this.hash(data.own_number)
}).exec(function(error, mobile_number) {
mobileCreateCallback(error, mobile_number, user);
});
};
User.create({
username: data.username,
password: data.password,
device_id: data.device_id,
own_number: data.own_number,
mobile_numbers: data.mobile_numbers
}).exec(userCreateCallback);
}
You should reed about callbacks: callbackhell and asynchronous functions in node.js/sails.js.

Promisifying bcrypt-nodejs with Bluebird

I'm using NodeJS, with bcrypt-nodejs (https://github.com/shaneGirish/bcrypt-nodejs) and Bluebird for promises. Came up with this code and been wondering if there is better way to do the same thing. I have module with:
var Promise = require("bluebird"),
bcrypt = Promise.promisifyAll(require('bcrypt-nodejs'));
// ....[some othe code here]
Users.prototype.setPassword = function(user) {
return bcrypt.genSaltAsync(10).then(function(result) {
return bcrypt.hashAsync(user.password, result);
});
};
then from another module I call users.setPassword as below:
app.post('/api/v1/users/set-password', function(req, res, next) {
users.setPassword(req.body).then(function(result) {
// Store hash in your password DB.
console.log(result[1]);
res.json({
success: true
})
})
.catch(function(err) {
console.log(err);
});
});
It always ends up with "[Error: No callback function was given.]" message as bcrypt.hashAsync seems to require 4 parameters. Original, non-promisified hash method requires 3 only though. When I add empty callback to hashAsync, it works fine:
Users.prototype.setPassword = function(user) {
return bcrypt.genSaltAsync(10).then(function(result) {
return bcrypt.hashAsync(user.password, result,function() {});
});
};
Is there any better way to do this, without providing empty callback as above?
EDIT:
In response to Bergi's comment.. the function will set password eventually, I just didn't get that far when posted the question. Now got this far, please let me know if something is not quite right though:
Users.prototype.setPassword = function(user) {
return bcrypt.genSaltAsync(10).then(function(result) {
return bcrypt.hashAsync(user.password, result, null);
})
.then(function(result) {
// store in database
console.log("stored in database!");
return result;
});
};
bcrypt.hashAsync seems to require 4 parameters. Original, non-promisified hash method requires 3 only though.
It's the other way round rather. From the docs:
hash(data, salt, progress, cb)
data - [REQUIRED] - the data to be encrypted.
salt - [REQUIRED] - the salt to be used to hash the password.
progress - a callback to be called during the hash calculation to signify progress
callback - [REQUIRED] - a callback to be fired once the data has been encrypted.
The original method took 4 arguments, hashAsync will take 3 and return a promise.
However, in your code you were only passing two. You don't need to pass an empty function though, that the parameter is not [REQUIRED] means you can pass null (or any other falsy value) for it. bcrypt will create such an empty function itself. So use
function (data) {
return bcrypt.genSaltAsync(10).then(function(result) {
return bcrypt.hashAsync(data.password, result, null);
});
}
This is my promisified bcrypt from a project I did a while back. Bluebird isn't really necessary for such a small, simple library.
module.exports = {
makeUser: function(username, password){
return new Promise(function(resolve, reject) {
bcrypt.genSalt(10, function(err, salt){
bcrypt.hash(password, salt, null, function(err, hash) {
if (err) {
console.log("hashing the password failed, see user.js " + err);
reject(err);
}
else {
console.log("hash was successful.");
resolve(hash);
}
})
})
})
.then(function(hash){
return db.createUser(username, hash)
})
},
login: function(username, password){
return db.userFind(username)
.then(function(userObj){
if(!userObj){
console.log("did not find " + username + " in database.");
return new Promise(function(resolve, reject){
resolve({login:false, message:"Your username and/or password are incorrect."})
}
}
else {
console.log("found user: " + userObj._id, userObj);
return new Promise(function(resolve, reject){
bcrypt.compare(password, userObj.hashword, function(err, bool) {
resolve({bool:bool,
user:userObj._id,
mindSeal: userObj
})
})
})
}
})
}
}
Example Usage:
app.post('/signup', function(req, res) {
var username = req.body.username;
var password = req.body.password;
var user = handler.userExists(username)
.then(function(answer){
if (answer !== null){
console.log(req.body.username + " was taken")
res.send({login: false, message: req.body.username + " is taken"});
return null;
} else if (answer === null) {
console.log("username not taken")
return handler.makeUser(username, password);
}
})
.catch(function(err){
console.log("error during user lookup:", err);
res.status(404).send({message:"database error:", error:err});
})
if (user !== null){
user
.then(function(x){
console.log("this is returned from handler.makeUser: ", x)
console.log(x.ops[0]._id)
req.session.user = x.ops[0]._id;
var mindSeal = {
userSettings: {
username: x.ops[0]._id,
newCardLimit: null,
tValDefault: 128000000,
lastEdit: req.body.time,
todayCounter: 0,
allTimeCounter: 0,
cScaleDefault: {0: 0.9, 1: 1.2, 2: 1.8, 3: 2.5},
accountMade: req.body.time
},
decks: {}
};
handler.setMindSeal(req.session.user, mindSeal, req.body.time);
res.send({
login: true,
mindSeal: mindSeal
});
})
.catch(function(error){
console.log("make user error: " + error);
res.status(401).send({message:"failed.",error:error,login:false});
})
}
});
app.post('/login', function(req, res) {
var username = req.body.username;
var password = req.body.password;
handler.login(username, password)
.then(function(obj){
if (obj.bool){
console.log("username and password are valid. login granted.");
req.session.user = obj.user;
console.log("obj is:", obj)
var mindSeal = {decks:obj.mindSeal.decks, userSettings:obj.mindSeal.userSettings};
console.log("mindSeal sending:", mindSeal);
res.status(200).send({
login: true,
message:"Login Successful",
mindSeal: obj.mindSeal
});
}
else {
console.log("password invalid")
res.status(401).send({login: false, message:"Your username and/or password are incorrect."})
}
})
.catch(function(error){
console.log(error);
res.status(404).send({message:"database error:", error:err});
})
});
conceptual example only; borrowed and slightly modified some old code of mine on the fly. Working code (I see things I'd like to improve in it now, but it works) here: https://github.com/finetype/mindseal/blob/master/server.js
Maybe you could use another bcrypt library, with a better API which removes the need for promises.
Users.prototype.setPassword = function(user) {
return TwinBcrypt.hashSync(user.password, 10);
};
Or, with progress tracking :
Users.prototype.setPassword = function(user) {
function progress(p) {
console.log( Math.floor(p*100) + '%' );
}
TwinBcrypt.hash(user.password, 10, progress, function(result) {
// store in database
console.log("stored in database!");
return result;
});
};

Node.js moongoose database count undefined

I have the following function in one of my mongoose models:
UserSchema.methods.checkUsernameExists = function checkUsernameExists(req){
User.count({ username: req.body.username }, function(err, count){
if(err){
return console.error(err);
}
console.log(count); //Logs 2
});
}
Now when I log it, it gives the correct count. But if I return the count and try doing this, in one of my controllers it returns undefined.
var User = require("../models/user").User;
var user = new User();
exports.signup = function(req, res){
var count = user.checkUsernameExists(req);
console.log(test)
}
Any help would be appreciated thank you.
Remember that Node works asynchronously, meaning that you can't return a normal value from a function that executes an asynchronous function itself, like your checkUsernameExists does.
The most common way to deal with this is by passing a callback function which is called when the value is retrieved:
UserSchema.methods.checkUsernameExists = function checkUsernameExists(req, callback) {
User.count({ username: req.body.username }, callback);
};
This will pass the err and count variables that are the result of User.count as arguments to the callback function you supply. To use:
user.checkUsernameExists(req, function(err, count) {
if (err) {
console.error(err);
} else {
console.log('the count is', count);
}
});
To make the function to what it's name suggests, namely to 'return' a boolean to signify if a username already exists, you might use something like this:
UserSchema.methods.checkUsernameExists = function checkUsernameExists(req, callback) {
User.count({ username: req.body.username }, function(err, count) {
if (err) {
callback(err);
} else {
callback(null, count !== 0);
}
});
};

Async waterfall equivalent with Q

I've got a single page which is an account settings page. In it, I allow my users to update their avatar (if they've attached an image), change their email (if it has been changed from the original), and change their name and password.
Right now, I'm using async's waterfall method, but am swapping out async for Q since I prefer the syntax (and api). I'm wondering if this is the way that I should be using Q in replacement of async's waterfall.
I'm doing something like this:
exports.settingsAccountPOST = function(req, res) {
var doesEmailExist = function() {
var deferred = Q.defer();
User.findByEmail({
email: req.body.email
}, function(err, user) {
if (err) {
deferred.reject(err);
} else {
deferred.resolve(user);
}
});
return deferred.promise;
};
var updateEmail = function(email) {
var deferred = Q.defer();
User.updateEmail({
userId : req.session.user.id,
email : req.body.email
}, function(err, updated) {
if (err) {
deferred.reject(err);
} else {
deferred.resolve(updated);
}
});
return deferred.promise;
};
var updateName = function() {
var deferred = Q.defer();
if (req.body.name) {
User.updateName({
userId: req.session.user.id,
name: req.body.name
}, function(err, updated) {
if (err) {
deferred.reject(err);
} else {
deferred.resolve(updated);
}
});
return deferred.promise;
}
};
doesEmailExist().then(function(email) {
if (!email) {
return(updateEmail(email));
}
}).then(function() {
return(updateName())
}).then(function() {
res.redirect('/account')
});
};
Say that there is an error with the email address being used. Is there a way to "pass" it to the final call? Use case: Updated password properly, but email update didn't work, so I want to show a session flash to the user telling them they updated their password properly, but there was an issue with updating their email.
I was looking in the docs and it seems I may need to use:
.fin(function () {
});
Is this correct? If so, what should I be passing into that? Just push to an object the error that occurred within the chain and then loop through all errors and display them to the user? Or just return immediately and display the error?
If you are using Q.defer you are generally doing something wrong.
var findByEmail = Q.nbind(User.findByEmail, User);
var updateEmail = Q.nbind(User.updateEmail, User);
var updateName = Q.nbind(User.updateName, User);
//later on...
exports.settingsAccountPOST = function (req, res) {
findByEmail({
email: req.body.email
})
.then(function (user) {
if (!user) {
return updateEmail({
userId: req.session.user.id,
email: req.body.email
});
}
})
.then(function () {
return updateName({
userId: req.session.user.id,
name: req.body.name
})
})
.then(function () {
res.redirect("/account");
})
.catch(function(e){
//Handle any error
});
};

Categories