Wrapping my head around Promise and then - node.js

Ive read plenty of articles on Promises and I feel i'm still missing something. Here is an example of what i'm trying to wrap my head around.
File: ad.js
// Using node.js activedirectory
class AD
{
constuctor(options){
this.ad = new ActiveDirectory(options);
}
isUserValid(user, password){
return new Promise((resolve, reject) =>{
ad.authenticate(user, password, (err, auth) ->{
if(err){
reject({ code: 500, message: 'Unknow error'});
}
if(auth){
resolve({code: 200, message: 'ok'});
}
});
)
}
}
module.exports = {
AD: AD
}
File: session.js
createSession(user, password){
var self = this;
return new Promise((resolve, reject) =>{
const ad = new AD("Required parameters");
Code: 1
const result = self._isADValid(ad, user, password);
Code: 2
self._isADValidPromise(ad, user, password)
.then(resp_ad =>{
})
.catch(err =>{
});
);
}
_isADValidPromise(ad, user, password) {
return new Promise((resolve, reject) => {
ad.isUserValid(user, password)
.then(resp_ad => {
resolv(resp_ad);
})
.catch(err => {
reject(err);
});
});
}
_isADValid(ad, user, password) {
return ad.isUserValid(user, password)
.then(resp_ad => {
return resp_ad;
})
.catch(err => {
return err;
});
}
What i'm trying to understand is the following:
Shouldnt "Code 1" return a value from _isADValid. Instead its returning "Promise{ pending }". I though that you only need to use then/catch from a Promise?
So the function _isADValid() is calling the AD function isUserValid which is returning from a Promise and _isADValid() is wrapped in a then/catch which just returns the result.
Code 2 using "_isADValidPromise" works without any issues. And I get that its wrapped in a Promise so its doing what I expected.
If anyone can help clarify why Code 1 is not working

You have to remember that Promises don't resolve until (at least) the next tick (details here). You're expecting the Promise to return synchronously, but Promises are by definition asynchronous.
Your example demonstrates this: If you think about it, your call to authenticate is very likely asynchronous - reaching out to some server to authenticate the user. Promises were invented to wrap asynchronous operations such as this one in a way that provides a nice control flow and avoids callback hell. So in short, any time you use a Promise, it will return asynchronously.
In that light, self._isADValid(ad, user, password); returns a Promise, so naturally, result is a Promise. The only way to get the result out of it is to add a .then() and wait for it to resolve, which will always be at least one tick into the future.
Hope that helps.

Related

What's the proper way to return a promise in this triggered Cloud Function

I understand that for a triggered function, I must always return a promise. Look at the following example:
//Example
exports.onAuthUserDelete = functions.auth.user().onDelete(async (user) => {
let userId = user.uid;
try {
await firestore.collection('Users').doc(userId).delete();
return Promise.resolve();
} catch (error) {
logger.error(error);
return Promise.reject(error);
}
});
My questions are:
Is return Promise.resolve() required or can I just do return firestore.collection('Users').doc(userId).delete()? If I opt to go with the latter, what would happen if the command failed? Will it still trigger catch()?
Is it better to just start every function with the following template to make sure a promise is always returned?
//Is it better to start with this boilerplate
exports.onAuthUserDelete = functions.auth.user().onDelete(async (user) => {
return new Promise((resolve, reject) => {
//My code goes here...
});
}
Firestore's delete operation already returns a promise, so there's no need to create your own. As far as I can see that first example is example the same as:
exports.onAuthUserDelete = functions.auth.user().onDelete((user) => {
return firestore.collection('Users').doc(user.uid).delete();
});
Given that, I highly recommend using the above shorter version.

Async - Await issue using Twitter API

I'm currently trying to practice using an API with Twitter API.
I'm using Twit package to connect to twitters API but when I try to do a get request I get
Promise { pending }
I have tried using Async-Await but I'm not sure what I'm doing wrong here.
Here is my code:
const Twit = require('twit');
const twitterAPI = require('../secrets');
//Twit uses OAuth to stablish connection with twitter
let T = new Twit({
consumer_key: twitterAPI.apiKey,
consumer_secret: twitterAPI.apiSecretKey,
access_token: twitterAPI.accessToken,
access_token_secret: twitterAPI.accessTokenSecret
})
const getUsersTweets = async (userName) => {
let params = { screen_name: userName, count: 1 }
const userTweets = await T.get('search/tweets', params, await function (err, data, response) {
if (err) {
return 'There was an Error', err.stack
}
return data
})
return userTweets
}
console.log(getUsersTweets('Rainbow6Game'));
Problem
The biggest assumption that is wrong with the sample code is that T.get is expected to eventually resolve with some data.
const userTweets = await T.get('search/tweets', params, await function (err, data, response) {
if (err) {
return 'There was an Error', err.stack
}
return data // 'data' returned from here is not necessarily going to be received in 'userTweets' variable
})
callback function provided as last argument in T.get function call doesn't have to be preceded by an 'await'.
'data' returned from callback function is not necessarily going to be received in 'userTweets' variable. It totally depends on how T.get is implemented and can not be controlled.
Reason
Thing to be noted here is that async / await works well with Promise returning functions which eventually get resolved with expected data, however, that is not guaranteed here
Relying on the result of asynchronous T.get function call will probably not work because it returns a Promise { pending } object immediately and will get resolved with no data. The best case scenario is that everything with your function will work but getUsersTweets function will return 'undefined'.
Solution
The best solution is to make sure that your getUsersTweets function returns a promise which eventually gets resolved with correct data. Following changes are suggested:
const getUsersTweets = (userName) => {
return new Promise ((resolve, reject) => {
let params = { screen_name: userName, count: 1 }
T.get('search/tweets', params, function (err, data, response) {
if (err) {
reject(err);
}
resolve(data);
})
}
}
The above function is now guaranteed to return expected data and can be used in the following way:
const printTweets = async () => {
const tweets = await getUsersTweet(userName);
console.log(tweets);
}
printTweets();
From what I can see on your code, getUserTweets is an async function, so it will eventually return a promise. I'm assuming you will use this value on another function, so you will need to use it inside an async function and use await, otherwise you will always get a promise.
const logTweets = async (username) => {
try {
const userTweets = await getUsersTweets(username);
// Do something with the tweets
console.log(userTweets);
catch (err) {
// catch any error
console.log(err);
}
}
If logging is all you want and you wrapped it inside a function in which you console.log it, you can call that function directly:
logTweets('someUsername');

node.js middleware making code synchronous

I am trying to make res.locals.info available on every single page.
I'm trying to do this by middleware but I'm getting an error.
Apparently res.locals.info is not ready yet when the page render, thus I get an error info is not defined. How do I solve this?
app.use(function(req,res,next){
async function getInfo(user) {
let result = await info.search(user);
setInfo(result);
}
function setInfo(result){
res.locals.info= result;
}
getInfo(req.user);
return next();
})
search():
module.exports.search= function (user) {
var query=`SELECT count(*) as Info from dbo.InfoUsers WHERE user= '${user}' ;`
return new Promise((resolve, reject) => {
sequelize
.query(`${query}`, {model: InformationUser})
.then((info) => {
resolve(info);
})
})
};
You were calling next() before your getInfo() function had done its work, thus res.locals.info had not yet been set when you were trying to use it.
An async function returns a promise. It does NOT block until the await is done. Instead, it returns a promise immediately. You will need to use await or .then() on getInfo() so you know when it's actually done.
If info.search() returns a promise that resolves to the desired result, then you could do this:
app.use(function(req,res,next){
// this returns a promise that resolves when it's actually done
async function getInfo(user) {
let result = await info.search(user);
setInfo(result);
}
function setInfo(result){
res.locals.info= result;
}
// use .then() to know when getInfo() is done
// use .catch() to handle errors from getInfo()
getInfo(req.user).then(result => next()).catch(next);
});
And, you can remove the deferred anti-pattern from your search function and fix the error handling (which is a common issue when you use the anti-pattern). There is no need to wrap an existing promise in another promise.:
module.exports.search = function (user) {
var query=`SELECT count(*) as Info from dbo.InfoUsers WHERE user= '${user}' ;`
// return promise directly so caller can use .then() or await on it
return sequelize.query(`${query}`, {model: InformationUser});
};

Unable to use .then in express framework

I'm new to Express framework and learning, I'm having a problem using .then. The problem is I have 2 functions and I want the first one to complete before the second to start executing. I'm exporting the modules.
var ubm = require('./userBasic');
There are 2 functions setUserData and showUserId, the showUserId must execute only after setUserData has performed its operation.
var userId = ubm.setUserData(userName,userEmail,userDOB,moment);
userId.then(ubm.showUserId(userId));
Below are the 2 functions:
module.exports = {
setUserData: function (userName,userEmail,userDOB,moment){
//Performing database activities
return userId;
}
showUserId: function (userId){
console.log(userId);
}
}
When i run it says TypeError: Cannot read property 'then' of undefined.
Like I said I'm very new and learning and was unable to figure out the solution. I did some google search and got a brief about promise, but I don't know how to implement here.
Try using promises
module.exports = {
setUserData: function(userName, userEmail, userDOB, moment) {
return new Promise(function(resolve, reject) {
//db stuff
reject(error);
resolve(userId);
});
},
showUserId: function(userId) {
console.log(userId);
};
};
So in your execution you would write
ubm.setUserData(username, usereEmail, userDOB, moment)
.then((data) => {
showUserId(data);
})
.catch((err) => {
console.log(err);
});
A couple of things to note is that in this instance you could just log data without the need for another function like
ubm.setUserData(username, usereEmail, userDOB, moment)
.then((data) => {
console.log(data);
})
.catch((err) => {
console.log(err);
});
Whatever value you pass into resolve() will be returned as well as you pass errors into reject().

Am I using promises correctly?

I have the following function (I'm using the Q promise library):
confirmEmail: function(confirmationCode){
var deferred = q.defer();
User.find({
where: {confirmation_code: confirmationCode}
}).then(function(user){
if(user){
user.updateAttributes({
confirmation_code : null,
confirmed: true
}).then(function() {
deferred.resolve();
});
}else{
deferred.reject(new Error('Invalid confirmation code'));
}
});
return deferred.promise;
}
I've been reading a bit about the best practices regarding promises e.g. What is the explicit promise construction antipattern and how do I avoid it?
http://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html
Have I written the above function so that it is keeping with these practices, or is there a better way?
It seems to me like you can rewrite your method to this:
confirmEmail : function(confirmationCode) {
return User.find({
where : { confirmation_code : confirmationCode }
}).then(function(user) {
if (! user) {
throw new Error('Invalid confirmation code');
}
return user.updateAttributes({
confirmation_code : null,
confirmed : true
});
});
}
Both User.find() and user.updateAttributes() seem to be returning promises (I'm inferring this from your code), so you can easily create a promise chain with them.
But even if they weren't returning promises, you probably wouldn't have needed q.defer(), as outlined on this page you already mention ("Rookie mistake #4"). See Q.Promise.
With Mongoose (especially from version 4), promises are supported natively, so you don't need to use Q. Also, on Node.js 0.12+ and io.js there's natie support for Promises, so again no need for Q!
This is how I would write the method (using the native Promise; here a polyfill if still on Node 0.10)
confirmEmail: function(confirmationCode){
return new Promise(function(resolve, reject) {
User.find({
where: {confirmation_code: confirmationCode}
})
.then(function(user) {
if(user){
user.updateAttributes({
confirmation_code : null,
confirmed: true
})
.then(resolve, reject)
}
else {
reject(new Error('Invalid confirmation code'))
}
}, reject)
})
}
How it works:
1. The confirmEmail method returns one Promise. As per pattern, the promise can be either resolve()'d or reject()'d
2. User.find is invoked. With Mongoose, that returns a promise, so you can do: then(callback, reject). So, if User.find returns an error, the Promise returned by confirmEmail will be rejected (the "outer one").
3. If User.find succeeds, we proceed. If there is no result (if(user) is false), then we manually reject the "outer promise" and we don't do anything else.
4. If there's a user, instead, we call user.updateAttributes, which too returns a promise. We're thus invoking then(resolve, reject) so the result of that promise will be passed to the "outer promise".
Example use:
obj.confirmEmail(confirmationCode).
.then(function(user) {
// Everything went fine
}, function(error) {
// This is invoked if: User.find() failed, or if no result was obtained (with the error "Invalid confirmation code") or if user.updateAttributes failed
})

Resources