How to handle Recursive Promise - javascript

function Auth() {
this.accessTokenError = false;
}
Auth.prototype.validateToken = function (accessToken, refreshToken) {
var token;
var self = this;
return new Promise(function (resolve, reject) {
AuthCron.secret()
.then(function (secret) {
if (self.accessTokenError) {
token = refreshToken;
secret = secret.substr(0, secret.length / 2);
}
else
token = accessToken;
JWT.verify(token, secret, function (error, decoded) {
console.log(error, decoded);
if (error) {
if (!self.accessTokenError) {
self.accessTokenError = true;
// I don't know how to handle this
self.validateToken(accessToken, refreshToken)
}
else {
self.accessTokenError = false;
reject(error);
}
}
else
resolve(decoded.user);
});
})
.catch(function (err) {
reject(err)
});
})
};
I am bit confused on how to handle recursive promise. The problem here is first promise never resolves nor rejects. What is the best way to handle this situation? This function accepts two tokens if access token is expired or invalid then refresh token is validated, if refresh token is also invalid then promise should reject.

With problems like this, it's generally best to separate the creation of promises from the higher order application logic. This is done by promisifying at the lowest possible level - JWT.verify() in this case.
JWT.verifyAsync = function(token, secret) {
return new Promise((resolve, reject) => {
JWT.verify(token, secret, (error, decoded) => {
error ? reject(error) : resolve(decoded);
});
});
};
Now, you could still write the Auth.prototype.validateToken() method to perform its tries recursively, but, as there would only ever be a maximum of two levels of recursion, it's far simpler to hard-code both tries with (pseudo-code) first_try().catch(() => second_try()). As a bonus, the need for that awkward .accessTokenError boolean disappears.
Here it is in full :
Auth.prototype.validateToken = function(accessToken, refreshToken) {
return AuthCron.secret()
.then(secret => {
return JWT.verifyAsync(accessToken, secret) // first try
.catch(() => JWT.verifyAsync(refreshToken, secret.substr(0, secret.length / 2))); // second try
})
.then(decoded => decoded.user);
};

Related

JavaScript promise not firing in order

I have been trying to effectively manage how I build my promises in my Express.js app.
Right now I have the following scenario: During the signup process, a user has an optional Organization Name field. If this is filled in, I need to create the Organization object and then add the _id of it to the other information that I am applying to the user. If there is no Organization name, proceed and update the basic user info.
<-- Right now the user is being updated before the organization is being created. -->
//basic info passed from the signup form
info['first_name'] = req.body.first_name;
info['last_name'] = req.body.last_name;
info['flags.new_user'] = false;
//if organization name is passed, create object and store _id in user object.
let create_organization = new Promise(function(resolve, reject) {
if (req.body.organization_name !== "") { //check if name is sent from form
Organization.create({
name: req.body.organization_name
}, function(err, result) {
console.log(result);
if (!err) {
info['local.organization'] = result._id;
resolve()
} else {
reject()
}
})
} else {
resolve()
}
});
let update_user = new Promise(function(resolve, reject) {
User.update({
_id: req.user._id
}, info, function(err, result) {
if (!err) {
console.log("Updated User!"); < --prints before Organization is created
resolve();
} else {
reject();
}
})
});
create_organization
.then(function() {
return update_user;
})
.then(function() {
res.redirect('/dash');
})
Nothing in your code waits for the first promise to settle before proceeding with starting the subsequent work. The work is started as soon as you call User.update, which is done synchronously when you call new Promise with that code in the promise executor.
Instead, wait to do that until the previous promise resolves. I'd do it by wrapping those functions in reusable promise-enabled wrappers (createOrganization and updateUser):
// Reusable promise-enabled wrappers
function createOrganization(name) {
return new Promise(function(resolve, reject) {
Organization.create({name: name}, function(err, result) {
console.log(result);
if (err) {
reject(err);
} else {
resolve(result);
}
});
});
}
function updateUser(id, info) {
return new Promise(function(resolve, reject) {
User.update({_id: id}, info, function(err, result) {
if (err) {
reject(err);
} else {
resolve();
}
})
});
}
(You may be able to use util.promisify or the promisify npm module to avoid doing that manually.)
And then:
//basic info passed from the signup form
info['first_name'] = req.body.first_name;
info['last_name'] = req.body.last_name;
info['flags.new_user'] = false;
//if organization name is passed, create object and store _id in user object.
(req.body.organization_name === "" ? Promise.resolve() : createOrganization(req.body.organization_name))
.then(function() {
return updateUser(req.user._id, info);
})
.catch(function(error) {
// handle/report error
});
(I stuck to ES5-level syntax since your code seemed to be doing so...)
See, so called 'executor function', passed into Promise constructor, is invoked immediately. That's why you essentially have a race condition here between two remote procedure calls. To solve this, make the update responsible for promise creation instead:
function updateUser(userId, userInfo) {
return new Promise(function(resolve, reject) {
User.update({_id: userId}, userInfo, function(err, result) {
if (err) {
reject(err);
}
else {
resolve(result);
}
});
});
}
... and call this function in then(). By doing this, an executor function will be called only when updateUser is invoked - and that'll be after createOrganization() finished its job.

Node.js Mongoose Promise getting lost

I have a Node.js API with a mongoDB. There is a route that creates a user and needs to hash the password, for this I use the bcryptjs package.
the route looks like this:
router.route('/user')
.post(function(req, res) {
if(req.body.password === req.body.passwordConfirm) {
userManager.addUser(req.body)
.then(function(response) { // waiting for the result of the mongoDB save
res.send({data:response});
});
} else {
res.send({err:'passwords do not match'});
}
})
and userManager.addUSer:
this.addUser = function(userobject) {
bcrypt.genSalt(10, function(err, salt) { // generate a salt
if(err !== null) {
console.log(err);
} else {
bcrypt.hash(userobject.password_hash, salt, function(err, hash) { // hash pw
if(err !== null) {
console.log(err);
else {
userobject.password_hash = hash; // store hash in user obj
var user = new User(userobject);
return user.save().catch(function(err){ // save user in mongoDB
console.log(err);
});
}
});
}
});
};
I get an error saying: "Cannot read property 'then' of undefined", which tells me that I am not receiving a promise from addUser. I looked and bcryptjs sadly does not use promises, however, mongoose does.
(adding this:
var mongoose = require('mongoose').Promise = Promise;
didn't help)
I tried wrapping the function in a promise with reject and resolve, but that gives this error: "TypeError: Promise resolver undefined is not a function".
How do I get the promise that the save() function of mongoose returns back to the .then() in the post route? I tried adding return in front of the two bcrypt function but that didn't work either..
Any suggestions are welcome!
Your addUser function nevers returns the promise to its caller. You're doing a return from the bcrypt.hash callback function, but that has nothing to do with addUser's return value.
It looks like addUser has to use some non-Promise-enabled APIs, so you're stuck with doing new Promise, something like this (see the *** comments):
this.addUser = function(userobject) {
return new Promise(function(resolve, reject) { // ***
bcrypt.genSalt(10, function(err, salt) { // generate a salt
if(err !== null) {
reject(err); // ***
} else {
bcrypt.hash(userobject.password_hash, salt, function(err, hash) { // hash pw
if(err !== null) {
reject(err); // ***
else {
userobject.password_hash = hash; // store hash in user obj
var user = new User(userobject);
resolve(user.save()); // *** save user in mongoDB
}
});
}
});
});
};
Also note that I don't have addUser just swallowing errors; instead, they're propagated to the caller. The caller should handle them (even if "handling" is just logging).
You do not return a Promise form your this.addUser, you have to convert your callback based bcrypt to Promises. You can convert the whole bcrypt API to support Promise based functions using e.g. promisifyAll of the bluebird library, or do it manually using new Promise like this way:
this.addUser = function(userobject) {
return new Promise((resolve, reject) => {
bcrypt.genSalt(10, (err, salt) => {
if (err) {
reject(err);
} else {
bcrypt.hash(userobject.password_hash, salt, function(err, hash) {
if (err) {
reject(err)
} else {
resolve(hash)
}
})
}
});
})
.then(hash => {
userobject.password_hash = hash; // store hash in user obj
var user = new User(userobject);
return user.save() // save user in mongoDB
})
.catch(function(err) {
console.log(err);
});
}
Or that way:
this.addUser = function(userobject) {
return new Promise((resolve, reject) => {
bcrypt.genSalt(10, (err, salt) => {
if (err) {
reject(err);
} else {
resolve(salt);
}
});
})
.then(salt => {
return new Promise((resolve, reject) => {
bcrypt.hash(userobject.password_hash, salt, function(err, hash) {
if (err) {
reject(err)
} else {
resolve(hash)
}
})
})
})
.then(hash => {
userobject.password_hash = hash; // store hash in user obj
var user = new User(userobject);
return user.save() // save user in mongoDB
})
.catch(function(err) {
console.log(err);
});
}
After doing some more digging in the change logs of bcryptjs I found out that they added promises but did not update the documentation.. The genSalt en hash methods will return a promise if the callbacks are omitted. This would translate to:
this.addUser = function(userobject) {
return bcrypt.genSalt(10).then((salt) => {
return bcrypt.hash(userobject.password, salt).then((hash) => {
userobject.password_hash = hash;
var user = new User(userobject);
return user.save();
});
});
};

Async function never returns

I'm using Node version 7.6.0 to try out the native async and await features.
I'm trying to figure out why my async call just hanging never actually resolves.
NLP module:
const rest = require('unirest')
const Redis = require('ioredis')
const redis = new Redis()
const Promise = require('bluebird')
const nlp = {}
nlp.queryCache = function(text) {
return new Promise(function(resolve, reject) {
redis.get(text, (err, result) => {
if (err) {
console.log("Error querying Redis: ", err)
reject(new Error("Error querying Redis: ", err))
} else {
if (result) {
let cache = JSON.parse(result)
console.log("Found cache in Redis: ", cache)
resolve(cache)
} else {
resolve(null)
}
}
})
})
}
nlp.queryService = function(text) {
console.log("Querying NLP Service...")
return new Promise(function(resolve, reject) {
rest.get('http://localhost:9119?q=' + text)
.end((response) => {
redis.set(text, JSON.stringify(text))
resolve(response.body)
})
})
}
nlp.query = async function(text) {
try {
console.log("LET'S TRY REDIS FIRST")
let cache = await nlp.queryCache(text)
if (cache) {
return cache
} else {
let result = await nlp.queryService(text)
console.log("Done Querying NLP service: ", result)
return result
}
} catch (e) {
console.log("Problem querying: ", e)
}
}
module.exports = nlp
The module consumer:
const modeMenu = require('../ui/service_mode')
const nlp = require('../nlp')
const sess = require('../session')
const onGreetings = async function(req, res, next) {
let state = sess.getState(req.from.id)
if (state === 'GREET') {
let log = {
middleware: "onGreetings"
}
console.log(log)
let result = await nlp.query(req.text)
console.log("XXXXXXXX: ", result)
res.send({reply_id: req.from.id, message: msg})
} else {
console.log("This query is not not normal text from user, calling next()")
next()
}
};
module.exports = onGreetings;
I'm unable to get the code to proceed to following line:
console.log("XXXXXXXX: ", result)
I can see that the query was successful in the NLP module
Edit: Added console.log statement to response body
The most likely cause is an error in a Promise that you aren't catching. I find it helps to avoid try-catch in all but the top calling method, and if a method can be await-ed it almost always should be.
In your case I think the problem is here:
nlp.queryService = function(text) {
console.log("Querying NLP Service...")
return new Promise(function(resolve, reject) {
rest.get('http://localhost:9119?q=' + text)
.end((response) => {
redis.set(text, JSON.stringify(text)) // this line is fire and forget
resolve(response.body)
})
})
}
Specifically this line: redis.set(text, JSON.stringify(text)) - that line is calling a function and nothing is catching any error.
The fix is to wrap all your Redis methods in promises, and then always await them:
nlp.setCache = function(key, value) {
return new Promise(function(resolve, reject) {
redis.set(key, value, (err, result) => {
if (err) {
reject(new Error("Error saving to Redis: ", err));
} else {
resolve(result);
}
});
})
}
nlp.queryService = async function(text) {
console.log("Querying NLP Service...")
const p = new Promise(function(resolve, reject) {
rest.get('http://localhost:9119?q=' + text)
.end((response) => { resolve(response.body) });
// This is missing error handling - it should reject(new Error...
// for any connection errors or any non-20x response status
});
const result = await p;
// Now any issue saving to Redis will be passed to any try-catch
await nlp.setCache(text, result);
return;
}
As a general rule I find it's best practise to:
Keep explicit promises low level - have Promise wrapper functions for your rest and redis callbacks.
Make sure that your promises reject with new Error when something goes wrong. If a Promise doesn't resolve and doesn't reject then your code stops there.
Every call to one of these promise wrappers should have await
try-catch right at the top - as long as every Promise is await-ed any error thrown by any of them will end up in the top level catch
Most issues will either be:
You have a Promise that can fail to resolve or reject.
You call an async function or Promise without await.

I can't make my Promise to properly reject with a new Error message, caught from a try/catch

I have a Promise method that parses links from the web. It returns an Object which I try to access a link key from, but when this Object is empty, it somehow skips the if I have to check its length, causing a scandalous error. Below the codes.
First, the method that is a Promise to parse the links:
* parseReporter() {
const article = new Promise((resolve, reject) => {
parser.parseURL(`https://www.google.com/alerts/feeds/${this.googleAlertsUrlId}/${this.reporterUrlId}`, (err, parsed) => {
if (err) {
reject(new Error(err))
}
if (parsed.feed.entries.length === 0) {
reject(new Error('Nothing to parse'))
}
const link = parsed.feed.entries[0].link
const betterLink = link.substring(42, link.indexOf('&ct='))
mercury.parse(betterLink).then((data) => {
resolve(data)
}).catch((err) => {
reject(new Error(err))
})
})
})
return article
}
And then, here's the method that calls this parseReporter():
* _getLastestNews(userReporter) {
const reportersOperation = new ReportersOperation()
reportersOperation.googleAlertsUrlId = userReporter.url.split('/')[3]
reportersOperation.reporterUrlId = userReporter.url.split('/')[4]
try {
return yield reportersOperation.parseReporter()
} catch (e) {
this.addError(HTTPResponse.STATUS_INTERNAL_SERVER_ERROR, e.message)
return false
}
}
The error is caused when it tries to access link from parsed.feed.entries[0]. I've already logged out the length, and I confirmed to do work and show a number, but it insists on skipping it. Am I doing something wrong with the Promise it try/catch themselves?
reject doesn't "stop" or "return" from a function like return
Therefore, your code is checking for error conditions, but continuing on, as if the data is OK
By adding return before the call to reject, you'll stop this from happening
Just the area of code with changes shown:
// snip
if (err) {
return reject(new Error(err))
}
if (parsed.feed.entries.length === 0) {
return reject(new Error('Nothing to parse'))
}
const link = parsed.feed.entries[0].link
const betterLink = link.substring(42, link.indexOf('&ct='))
//snip
Besides, what Jaramonda suggested, you're also using an anti-pattern when you have a promise and you do resolve and reject in both paths. You can do that much more efficiently:
resolve(mercury.parse(betterLink));
But, what you really should do is you should promisify parser.parseURL() so you can write all the control flow logic using promises. This is much more foolproof and creates a reusable interface that uses promises that you can use elsewhere:
// make promisified version of parser.parseURL()
parser.parseURLP = function (url) {
return new Promise((resolve, reject) => {
parser.parseURL(url, (err, parsed) => {
if (err) return reject(new Error(err));
resolve(parsed);
});
});
};
function parseReporter() {
return parser.parseURL(`https://www.google.com/alerts/feeds/${this.googleAlertsUrlId}/${this.reporterUrlId}`).then(parsed => {
if (parsed.feed.entries.length === 0) {
throw new Error('Nothing to parse');
}
const link = parsed.feed.entries[0].link
const betterLink = link.substring(42, link.indexOf('&ct='))
return mercury.parse(betterLink).catch(err => {
// wrap error in Error object
throw new Error(err);
})
})
}

Correctly chaining Promises

I want to bind promises sequentially, inside a loop. I need this to user accounts, where the result of one operation depends on another.
I am trying to write a flat version - all code in one place, using bind. That's at least what I wanted. I wrapped promises around two create methods, as below:
function create(myApi, record) {
return new Promise(function (resolve, reject) {
myApi.create(record, function (err, result) {
if (err) reject(err);
else resolve(result);
});
});
}
function createUser(myApi, record) {
return new Promise(function (resolve, reject) {
myApi.createUser(record, function (err, result) {
if (err) reject(err);
else resolve(result);
});
});
}
Now, I want to create users in a loop as:
for ( var i = 0; i < dummyData.accounts.length; i++) {
var cursorUser = dummyData.accounts[i];
var auth0User = {
email: cursorUser.email,
password: cursorUser.password,
connection: 'Username-Password-Authentication'
};
createUser(api, auth0User)
.then( function(auth0Info) {
console.log("Auth0 userInfo: ", auth0Info);
cursorUser.authProfile = auth0Info;
create(accountsAPIService, cursorUser)
.then(function (account) {
console.log("created account:", account);
})
.catch(function (err) {
console.log('count not create account for user, error: ', err, '\nfor: ', auth0User);
});
})
.catch(function (err) {
console.log('could not create auth0 user, error: ', err, '\nfor: ', auth0User);
});
}
Since the two method are asynchronous, it is of course not working correctly. Calls are not executed sequentially. I want to chain promises so that create does not run until a call from createUser returned. Tried using bind, but it did not work for me. It is how one should do the sequential chaining? I bind on .then of the createUser? Please advise.
When you return a promise from a then, the then chained after will resolve/reject with that promise instead of the original promise.
createUser(api, auth0User).then(function(auth0Info) {
cursorUser.authProfile = auth0Info;
// Return create's promise
return create(accountsAPIService, cursorUser);
}, function (err) {
console.log('could not create auth0 user, error: ', err, '\nfor: ', auth0User);
})
// This will wait for create's promise instead of createUser's promise
.then(function (account) {
console.log("created account:", account);
}, function (err) {
console.log('count not create account for user, error: ', err, '\nfor: ', auth0User);
})
Using ES6 you can use generators which allows you to do write async task as they were async. In this example i am using bluebird but ofc there are others great valid options.
var CreateUserGenerator = BPromise.coroutine(function * (arg) {
for ( var i = 0; i < dummyData.accounts.length; i++) {
var cursorUser = dummyData.accounts[i];
var auth0User = {
email: cursorUser.email,
password: cursorUser.password,
connection: 'Username-Password-Authentication'
};
var auth0Info = yield createUser(api, auth0User);
console.log("Auth0 userInfo: ", auth0Info);
cursorUser.authProfile = auth0Info;
var account = yield create(accountsAPIService, cursorUser)
console.log("created account:", account);
}
}
function generateorWithCatch (argument) {
creatLoopUser(arg)
.catch(function (err) {
console.log('could not create auth0 user, error: ', err, '\nfor: ', auth0User);
});
}
In this solution i assume your create and createUser functions are written and return value correctly

Categories