how to change async callback using promise in nodejs? - javascript

I am a beginner and am currently making a User Management system in NodeJS, I had previously done it with MongoDB, Express. Right now im making it all again with Express, Sequelize and Postgresql to better understand some concepts.
What im stuck at is the reset page where I previously used Async.waterfall to get the email id and send email using SendGrid, but now I want to know how can I convert it using Promises..? It is a bit confusing to understand how to use them with concurrent callbacks.
Here is the previous code using the async.waterfall :
app.post('/forgotpassword', function(req, res, next) {
async.waterfall([
function(done) {
crypto.randomBytes(20, function(err, buf) {
var token = buf.toString('hex');
done(err, token);
});
},
//2
function(token, done) {
User.findOne({ 'local.email': req.body.email }, function(err, user) {
if (!user) {
req.flash('forgotMessage', 'No account with that email address exists.');
return res.redirect('/forgotpassword');
}
user.local.resetPasswordToken = token;
user.local.resetPasswordExpires = Date.now() + 3600000; // 1 hour
user.save(function(err) {
done(err, token, user);
});
});
},
//3
function(token, user, done) {
var nodemailer = require('nodemailer');
var sgTransport = require('nodemailer-sendgrid-transport');
var options = {
auth: {
api_key: ''
}
};
var mailer= nodemailer.createTransport(sgTransport(options));
var mailOptions = {
to: user.local.email,
from: 'passwordreset#demo.com',
subject: 'Node.js Password Reset',
text: 'You are receiving this because you (or someone else) have requested the reset of the password for your account.\n\n' +
'Please click on the following link, or paste this into your browser to complete the process:\n\n' +
'http://' + req.headers.host + '/reset/' + token + '\n\n' +
'If you did not request this, please ignore this email and your password will remain unchanged.\n'
};
mailer.sendMail(mailOptions, function(err) {
req.flash('forgotMessage', 'An e-mail has been sent to ' + user.local.email + ' with further instructions.');
done(err, 'done');
});
}
],
//2 out of Async
function(err) {
if (err) return next(err);
res.redirect('/forgotpassword');
});
});

From async.waterfall documentation
Runs an array of functions in series, each passing their results to
the next in the array. However, if any of the functions pass an error
to the callback, the next function is not executed and the main
callback is immediately called with the error.
So its exactly the same job as Promise.then do, just chain your promises.
crypto.randomBytes(20)
.then( function (buf) {
var token = buf.toString('hex');
return token;
})
.then( function(token) {
return Model.User.findOne({where: {'email' : req.body.email}});
})
.then(function (user) {
if(!user){
// throw no user found error
}
return Model.User.create();
})
.catch( function(err) {
// error handling
// catch no user found error and show flash message
});
You have to have single catch in the end of promises chain, and then should not to be inside of another .then function. I can suggest to read this article - We have a problem with promises.

Related

Really confused with JS async (calling to 2 API)

So a simple explanation of what I am doing. First, I am accepting a GET request from an end user. Second, I am passing a (I am making a GET request now) parameter from the get request to an async function that returns a user's email address based on the parameter (essentially a vanity url. "Bob" vs "Robert Richardson").
Third, I am making on more GET request, to a second system, which returns a JSON file containing different tidbits about the user.
I know the callouts work normally because I can run node index.js with a function where I manually pass in the information I need, and it returns the desired results. But when I start my nodejs server, and try to respond to an incoming GET request (me testing, definitely not live) I get very weird results...
Here is simply what I am trying to get working at the moment:
//server.js
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const port = 8000;
app.use(bodyParser.urlencoded({
extended: true
}));
require('./app/routes')(app, {});
app.listen(port, () => {
console.log('test is listening on port :: ' + port);
});
//routes.js
const getdata = require('./getData '); //contains function for GET request one to convert param to email
async function getReturn(app, db) {
app.get('/getDataFrom/:alias', (req, res) => {
const alias = req.params.alias;
console.log('alias : ' + alias);
getEmailFromPromise(alias);
});
}
async function getEmailFromPromise(alias) {
console.log('getting email');
let output = await getEmail(alias);
console.log('output :: ' + output);
// return output;
}
function getEmail() {
return new Promise(resolve => {
let email = getdata.getUserInfo(alias);
resolve(email);
}, 1000);
}
Worth noting, JS and Node are not what I commonly code it, but I need to for a project I am helping out with. I can get simple examples to work, but not my one making requests.
Passing in GET: localhost:8000/getDataFrom/bob
The above returns the following:
test is listening on port :: 8000
alias : rrichardson
getting email
output :: undefined //I am wanting this to log after I actually get the email
email found :: robert.richardson#company.com //I do get this back, but out of order. The undefined above should also be this
The other file if it helps (it is exported properly):
//this is callout one getData
async function getUserInfo(alias){
client.connect();
client.query('select email, firstname, lastname from salesforce.user WHERE
alias = \''+ alias + '\'', (err, res) => {
if (err) console.log('err :: ' + err);
// console.log(res.rows);
client.end();
var email = res.rows[0].email;
if(email != null){
console.log('email found :: ' + email);
return email;
}
else return 'No Email Found';
});
}
Obviously I left out my credentials, but the last js file DOES work just fine when I pass specific data into it. Again, just issues when trying to respond to a GET request that is incoming to me. I would really appreciate any help clearing this up. I am pretty sure I can get the second GET request figured out if I can get some help figuring out this first one.
Thanks!
You need getReturn to return the promise generated by the getEmailFromPromise call. Because app.get uses a callback rather than a promise, you'll have to explicitly convert it to a promise first - in which case there's not much use in making getReturn async.
function getReturn(app, db) {
return new Promise((resolve) => {
app.get('/getDataFrom/:alias', (req, res) => {
const alias = req.params.alias;
console.log('alias : ' + alias);
getEmailFromPromise(alias)
.then(resolve);
});
});
}
The other problem is that getUserInfo also uses callbacks rather than promises - you can't just turn it into an async function, you'll have to explicitly convert it to return a Promise as well:
function getUserInfo(alias){
return new Promise((resolve, reject) => {
client.connect();
client.query('select email, firstname, lastname from salesforce.user WHERE
alias = \''+ alias + '\'', (err, res) => {
if (err) {
console.log('err :: ' + err);
reject(err);
}
// console.log(res.rows);
client.end();
var email = res.rows[0].email;
if(email != null){
console.log('email found :: ' + email);
resolve(email);
}
else resolve('No Email Found');
});
});
}
You also need to use await to consume promises if you want the line below to run only after the promise has resolved:
function getEmail() {
return new Promise(resolve => {
let email = await getdata.getUserInfo(alias);
But it doesn't make much sense to await something that's a promise if you're just going to resolve it immediately. (and the second argument you provide to the promise in getEmail, 1000, doesn't make any sense)
Just return the promise itself:
function getEmail() {
return getdata.getUserInfo(alias);
}
you are mixing async and promises, it would be avoid.
Your problem is th function getUserInfo is async but it has no "await" part, it is not waiting for "resolve" or return so it returns nothing or "undefined" because you ra anssigning that to the email in " let email = getdata.getUserInfo(alias);" you could use primse in this step, some like this:
function getUserInfo(alias){
return new Promise(function(resolve, reject){
client.connect();
client.query('select email, firstname, lastname from salesforce.user WHERE alias = \''+
alias + '\'', (err, res) => {
if (err) reject('err :: ' + err);
// console.log(res.rows);
client.end();
var email = res.rows[0].email;
if(email != null){
console.log('email found :: ' + email);
resolve(email);
}
else reject('No Email Found');
});
}
}
and call it:
function getEmail() {
return new Promise(resolve => {
getdata.getUserInfo(alias).then(function(email){
resolve(email);
});
});
}

Wait for AWS SNS publish callback to return a value to calling method

I am attempting to send a text message when a user requests to reset their password. I would like to wait for the message to be sent to alert the user if it was successful or not. I am currently attempting to do it as follows:
async function sendResetPasswordTextMessage(req, res) {
let result = {};
let phoneNumber = req.body.phoneNumber;
if (phoneNumber === undefined) {
return sendInvalidParametersMessage(res);
}
phoneNumber = phoneNumber.toString();
const userProfile = await models.UserProfile.findOne({
where: {
phoneNumber: phoneNumber
}
});
************************** RELEVANT CODE TO ISSUE *************************
if (userProfile) {
const message = "Your username is:\n" + userProfile.username;
const sent = await AWSSNSClient.sendMessage(message, phoneNumber);
if (!sent) {
result.error = setTitleAndMessage("Error", "An error occurred");
} else {
result.success = setTitleAndMessage("Success", "Message sent");
}
}
return res.send(result);
***************************************************************************
}
In my other class AWSSNSClient, I have the following sendMessage function:
function sendMessage(message, phoneNumber) {
const params = {
Message: message,
MessageStructure: "string",
PhoneNumber: "+1" + phoneNumber
};
let sent = false;
sns.publish(params, function(err, data) {
if (err) {
console.log(err, err.stack); // an error occurred
}
else {
sent = true;
}
});
return sent;
}
I cannot figure out how to make sendMessage wait for sns.publish to return before it returns itself. I have tried making it an async method and adding await on sns.publish, but the function still returns before sent gets set to true.
I know that the messages are sending without error because I am receiving them and no console logs are printed.
Stumbled on this one via Google trying to figure this out myself today - short answer that I am now using:
You can now do this with Async/Await — and Call the AWS service (SNS for example) with a .promise() extension to tell aws-sdk to use the promise-ified version of that service function (SNS) instead of the call back based version.
The only caveat here is the containing function must ALSO be async to utilize the await syntax.
For example:
let snsResult = await sns.publish({
Message: snsPayload,
MessageStructure: 'json',
TargetArn: endPointArn
}, async function (err, data) {
if (err) {
console.log("SNS Push Failed:");
console.log(err.stack);
return;
}
console.log('SNS push suceeded: ' + data);
return data;
}).promise();
The important part is the .promise() on the end there. Full docs on using aws-sdk in an async / promise based manner can be found here: https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/using-promises.html
In order to run another aws-sdk task you would similarly add await and the .promise() extension to that function (assuming that is available).
For anyone who runs into this thread and is actually looking to simply push multiple aws-sdk promises to an array and wait for that WHOLE array to finish (without regard to which promise executes first) I ended up with something like this:
let snsPromises = [] // declare array to hold promises
let snsResult = await sns.publish({
Message: snsPayload,
MessageStructure: 'json',
TargetArn: endPointArn
}, async function (err, data) {
if (err) {
console.log("Search Push Failed:");
console.log(err.stack);
return;
}
console.log('Search push suceeded: ' + data);
return data;
}).promise();
snsPromises.push(snsResult)
await Promise.all(snsPromises)
Hope that helps someone that randomly stumbles on this via google like I did!
stackdave will that actually wait?
Necevil "Search push suceeded will get logged twice" because you're mixing calling operations by passing a callback and using promises. You should only use one method of getting the result
let snsResult = await sns.publish({
Message: snsPayload,
MessageStructure: 'json',
TargetArn: endPointArn}).promise()
will do the trick
You can simply use callbacks for that. Modify your sendMessge like this
function sendMessage(message, phoneNumber, cb) {
const params = {
Message: message,
MessageStructure: "string",
PhoneNumber: "+1" + phoneNumber
};
sns.publish(params, cb);
}
then on your main file you can supply callback like this
if (userProfile) {
const message = "Your username is:\n" + userProfile.username;
AWSSNSClient.sendMessage(message, phoneNumber, (err, data) => {
if (err) {
result.error = setTitleAndMessage("Error", "An error occurred");
}
else {
result.success = setTitleAndMessage("Success", "Message sent");
}
res.send(result);
});
}
Here the right updated API, August 2018, Necevil answer send the sms twice.
// using config.env
AWS.config.region = 'eu-west-1';
AWS.config.update({
accessKeyId: process.env.AMAZON_SMS_ID,
secretAccessKey: process.env.AMAZON_SMS_TOKEN,
});
// parameters
let params = {
Message: contentSMS, // here your sms
PhoneNumber: mobile, // here the cellphone
};
const snsResult = await sns.publish(params, async (err, data) => {
if (err) {
console.log("ERROR", err.stack);
}
console.log('SNS ok: ' , JSON.stringify (data));
});
If you're having issues with duplicate SNS messages being sent, I fixed this issue by utilizing examples from AWS:
// Load the AWS SDK for Node.js
var AWS = require('aws-sdk');
// Set region
AWS.config.update({region: 'REGION'});
// Create publish parameters
var params = {
Message: 'MESSAGE_TEXT', /* required */
TopicArn: 'TOPIC_ARN'
};
// Create promise and SNS service object
var publishTextPromise = new AWS.SNS({apiVersion: '2010-03-31'}).publish(params).promise();
// Handle promise's fulfilled/rejected states
publishTextPromise.then(
function(data) {
console.log("Message ${params.Message} send sent to the topic ${params.TopicArn}");
console.log("MessageID is " + data.MessageId);
}).catch(
function(err) {
console.error(err, err.stack);
});
By utilizing a traditional .then() I was able to squash the duplicate message bug mentioned in comments above.
You can create a async function what use the promise method
async function sendMessage(message, phoneNumber){
const params = {
Message: message,
PhoneNumber: phoneNumber
};
return new Promise((resolve, reject) => {
SNS.publish(params, (err, data) => {
if (err) {
console.log("Search Push Failed:");
console.log(err.stack);
return reject(err);
} else {
console.log('Search push suceeded:' + phoneNumber);
return resolve(data);
}
})
});
}
and then you can call
var s= await sendMessage(message,phoneNumber);

Promise either never get called, or is rejected (Parse JS SDK)

I am trying to write a function that add or edit some fields on a User object.
The problem come when I try to save the user, if I use user.save, the Promise is rejected with error 206 UserCannotBeAlteredWithoutSessionError.
However, if I get the session id (and documentation about that is scarce), the promise never get resolve, nor rejected. The app seems to just jump to the callback.
My function:
function update(user, callback) {
let query = new Parse.Query(Parse.User);
query.equalTo("username", user.email);
query.find().then(
(users) => {
if(users.length === 0) {
callback('Non existent user');
} else {
let user = users[0];
// user.set('some', 'thing');
console.log('save');
user.save(/*{
sessionToken: user.getSessionToken()
}*/).then(
(test) => {
console.log('OK - ' + test);
callback();
}, (err) => {
console.log('ERR- ' + require('util').inspect(err));
// console.log(callback.toString());
callback(error.message);
}
);
}
},
(error) => {
callback(error.message);
}
);
}
Called with:
var async = require('async'),
baas = require('./baas.js');
async.waterfall([
(callback) => {
callback(null, {
email: 'user#test.com',
password: 'password'
});
},
(user, callback) => {
console.log('connect');
baas.connect(() => { //Initialize the connection to Parse, and declare use of masterKey
callback(null, user);
});
},
(user, callback) => {
console.log('update');
baas.update(user, (err) => {
callback(err);
});
}
], (err) => {
console.log('Error: ' + err);
});
The logs become:
Without session token:
connect
update
save
ERR- ParseError { code: 206, message: 'cannot modify user sA20iPbC1i' }
With session token:
connect
update
save
I do not understand how it is possible that the promise just callback without printing anything, nor why no error are raised anywhere.
Edit:
Following #user866762 advice, I tried to replace the query with Parse.User.logIn and use the resulting User object.
While this solution give me a sessionToken, the end result is the same, parse crash if I don t provide the session token, or give me a error if I do.
According to the Parse Dev guide:
...you are not able to invoke any of the save or delete methods unless the Parse.User was obtained using an authenticated method, like logIn or signUp.
You might also try becoming the user before saving, but I have my doubts that will work.
When you're "get[ting] the session id" my guess is that you're really breaking something. Either Parse is having a heart attack at you asking for the session token, or when you're passing it in save you're causing something there to explode.

How get values out of a callback? (NodeJs + Mongoose)

I'm not too good with callbacks, and now I have problems to find a document with mongoose but use the document in the same action/controller before send a response.
uploadFile = function(req,res) {
var _objs = {};
function retrieveUser(objs,username, callback) {
User.findOne({ 'username': username })
.exec(function(err, user){
if(err) callback(err,null,null);
else callback(null,user,objs);
});
}//retrieveUser()
retrieveUser(_objs,req.body.user,function(err,user,_objs) {
if(err) console.log('ERROR: ' + err);
_objs.user = user;
console.log(_objs.user);
});
console.log(_objs);
}
So, inside the callback function the console.log() shows the object rightly, but the second console.log() shows me _objs as empty. Well, I need to fill _objs with other objects as attributes, How can I acchieve that?
Everything is working as designed. Your code will be executed as follows:
retrieveUser(_objs,req.body.user,function(err,user,_objs) {
// will be executed when the retrieveUser function completes is tasks...
if(err) console.log('ERROR: ' + err);
_objs.user = user;
console.log(_objs.user);
});
// ...meanwhile, execution will continue here.
console.log(_objs); // depending on how fast retrieveUser calls the callback, _objs will be set or (more likely) not set.
You can either continue the control flow of your application inside the callback or use frameworks like Streamline.js, async.js, Step, Seq, IcedCoffeeScript, promise.js, ...
Well this is what I did. At really for what I expected that is something similar to what I do in controllers in others frameworks of others languages, query for some entities and create another one that will be related to the previous that was queried.
uploadPic = function(req, res, next) {
var username = req.body.user,
estId = req.body.est;
async.series([
function(callback) {
User.findOne({ 'username': username})
.exec(function(err, user){
if(err) return callback(err);
if(!user) return callback(new Error("No user whit username " + username + " found."));
callback(null,user);
});
},
function(callback) {
Est.findById(estId)
.exec(function(err,est) {
if(err) return callback(err);
if(!est) return callback(new Error("No Est with ID " + estId + " found"));
callback(null,est);
})
},
],function(err,results){
if(err) return next(err);
console.log(results);
/// do my own stuffs here!
});// end async.series()
}

Express + PassportJs : Why do we need to delay the execution of method with process.nextTick() in passport strategy?

I'm using passport local strategy for user registration. I came across this code example where author is using process.nextTick to delay the execution of a method inside Passport LocalStrategy callback. I understand that we can delay the execution of some method with process.nextTick and execute it in the next tick of the event loop but can you explain me why do we need to use it in this example?
passport.use('signup', new LocalStrategy({
usernameField: 'email',
passReqToCallback: true // allows us to pass back the entire request to the callback
},
function(req, email, password, done) {
findOrCreateUser = function() {
if (isValidEmail(email)) {
// find a user in Mongo with provided email
User.findOne({
'email': email
}, function(err, user) {
// In case of any error, return using the done method
if (err) {
console.log('Error in SignUp: ' + err);
return done(err);
}
// already exists
if (user) {
console.log('User already exists with email: ' + email);
return done(null, false, req.flash('message', 'User Already Exists'));
} else {
// if there is no user with that email
// create the user
var newUser = new User();
// set the user's local credentials
newUser.email = email;
newUser.password = createHash(password);
// save the user
newUser.save(function(err) {
if (err) {
console.log('Error in Saving user: ' + err);
throw err;
}
console.log('User Registration succesful');
return done(null, newUser);
});
}
});
} else {
console.log('Not a valid Email!');
return done(null, false, req.flash('message', 'Invalid Email!'));
}
}
// Delay the execution of findOrCreateUser and execute the method
// in the next tick of the event loop
process.nextTick(findOrCreateUser);
}));
this is because the author wants to keep the function always asynchronous. In the code snippet, if isValidEmail(user) returns false, this code is synchronous ( since it doesn't include any io operation). By delaying the execution, the error handler is called next tick so that this handle is always asynchronous no matter the email is valid or not.

Categories

Resources