I am refactoring my code using promises. I am running into a problem. I have two API routes. First one is api.js and the second is account.js. I also have 4 controllers(CommentController, ZoneController, ProfileController, AccountController) .
CommentController, ZoneController, and ProfileController share the same API route(api.js).
account.js uses AccountController. But AccountController's method uses ProfileController's method.
I ended up having Promise calling another Promise, but I am not returning data properly. It is leaving the server hanging. How can I return data when one Promise is calling another Promise?
Basically account.js is calling AccountController.js that has a method that calls ProfileController.js, but both AccountController and ProfileController are refactored to Promise. I am not getting data back. Please help.
AccountController.js
var ProfileController = require('./ProfileController');
module.exports = {
currentUser: function(req) {
return new Promise(function(resolve, reject) {
if (req.session == null) {
reject({message: 'User not logged in'});
return;
}
if (req.session.user == null) {
reject({message: 'User not logged in'});
return;
}
ProfileController.findById(req.session.user, function(err, result) {
if (err) {
reject({message: 'fail'});
return;
}
resolve(result);
return;
});
});
}
ProfileController.js
findById: function(id) {
return new Promise(function(resolve, reject){
Profile.findById(id, function(err, profile){
if(err){
reject(err);
return;
}
resolve(profile);
return;
});
})
},
account.js
router.get('/:action', function(req, res, next) {
var action = req.params.action;
if (action == 'logout') {
req.session.reset();
res.json({
confirmation: 'success',
message: 'Bye!'
});
return;
}
if (action == 'login') {
res.json({
confirmation: 'success',
action: action
});
return;
}
if (action == 'currentuser') {
AccountController.currentUser(req)
.then(function(result){
res.json({
confirmation: 'success',
user: result
});
return;
})
.catch(function(err){
res.json({
confirmation: 'fail',
message: err.message
});
return;
});
}
});
you should modify AccountController.js
AccountController.js
var ProfileController = require('./ProfileController');
module.exports = {
currentUser: function(req) {
return new Promise(function(resolve, reject) {
if (req.session == null) {
reject({message: 'User not logged in'});
return;
}
if (req.session.user == null) {
reject({message: 'User not logged in'});
return;
}
ProfileController.findById(req.session.user).then(
function (profile){
resolve(profile);
},function (err) {
reject(err);
});
}
Related
I am trying to validate if an email or username already exist in my mongoDB users collection in nodejs in my User Model.
But whenever i try to do that i get this error callback is not a function, when it finds that a username already exists in the next process that i am pushing to my processes array. meaning that the first process gets executed normally and there are no errors, but if an email doesnt already exists it goes to the next process and the error happens.
What am i doing wrong? How can I validate more than one field and return a response for that specific error?
here is my code :
register(params) {
return new Promise((resolve, reject) => {
var processes = [];
//check if the email is already registered
let user = new models.User()
processes.push((callback) => {
user.emailExists(params.email,null).then((response) => {
if(response && response.length) {
callback({errorCode: 403, msg: 'User Already Exists.'});
} else {
db.collection("Users").insertOne(options, function (error, response) {
if (error) {
callback(error)
} else {
console.log(response.ops[0])
if(response.ops[0]) {
callback(null, {result: response.ops[0]});
} else {
callback({errorCode: 403, msg: 'User sign-up failed.'})
}
}
})
}
}, (error) => {
reject(error);
});
});
processes.push((callback) => {
user.usernameExists(params.username).then((response) => {
if(response && response.length) {
callback({errorCode: 403, msg: 'username Already Exists.'});
} else {
db.collection("Users").insertOne(options, function (error, response) {
if (error) {
callback(error) //this is line 91
} else {
console.log(response.ops[0])
if(response.ops[0]) {
callback(null, {result: response.ops[0]});
} else {
callback({errorCode: 403, msg: 'User sign-up failed.'})
}
}
})
}
}, (error) => {
reject(error);
});
});
async.waterfall(processes, function (error, data) {
if (error) {
reject(error);
} else {
resolve(data);
}
})
})
}
emailExists(email, password) {
// check that required params are set
if (!email) {
return new Promise(function (resolve, reject) {
console.log("Parameters not set")
reject({errorCode: 403, msg: 'invalid.'});
});
}
return new Promise(function (resolve, reject) {
db.collection("Users").find({
"email": email.toLowerCase()
}).limit(1).toArray(function (error, response) {
if (error) {
reject(error);
} else {
if (response && response.length) {
if (!password) {
resolve(response);
} else {
bcrypt.compare(password, response[0].password, function (err, success) {
if (err) {
reject(err);
} else {
if (success) {
resolve(true);
} else {
resolve(false);
}
}
});
}
} else {
resolve(false);
}
}
});
});
}
usernameExists(params) {
if (!params || params.username) {
return new Promise(function (resolve, reject) {
console.log("params not set");
reject({errorCode: 403, msg: 'Invalid.'});
});
}
return new Promise(function (resolve, reject) {
db.collection("Users").find({
"username": params.username
}).limit(1).toArray(function (error, response) {
if (error) {
reject(error);
} else {
if (response && response.length) {
resolve(true)
} else {
resolve(false);
}
}
});
})
}
error log:
There are so many things wrong in the code that it's hard to point to something specific tbh.
Please read how to use waterfall:
An array of async functions to run. Each function should complete with any number of result values. The result values will be passed as arguments, in order, to the next task.
It means when you call
callback(null, {result: response.ops[0]});
the next process is being called with 2 arguments:
process({result: response.ops[0]}, callback)
you define your processes with only 1 argument, the rest are ignored. The first one is a json, so you get the error that it is not a function when you try to invoke it.
Now, how to fix it. Don't use waterfall. You don't need it. Bare async/await is more than enough and will make code much more readable.
I am using Slack API and I want to test does it work fine with response status code. Here is sending function :
sendMsg(msg) {
return this.slack.webhook({text: msg}, (err, res) => {
if (err) {
throw err;
}
console.log(res.statusCode) // = 200
return res.statusCode;
});
}
And my test:
it('Checks connection with Slack', (() => {
let slack = new Slack();
let res = slack.sendMsg('test');
expect(res).to.equal(200);
}));
But ofc. it's giving me request object to slack. I want to wait for response object from slack API. Thanks in advance.
It looks like slack.webhook takes in a callback, which is how you retrieve the status. The problem is that the caller of sendMsg has no way of getting that status.
One way to solve this is to have sendMsg take in a callback:
sendMsg(msg, onStatusReceived) {
this.slack.webhook({text: msg}, (err, res) => {
if (err) {
throw err;
}
console.log(res.statusCode) // = 200
onStatusReceived(res.statusCode);
});
}
Then in your test, use done to end the test when the callback is invoked:
it('Checks connection with Slack', (done) => {
let slack = new Slack();
slack.sendMsg('message', status => {
expect(status).to.equal(200);
done();
});
});
Another way is to have sendMsg wrap slack.webhook in a promise, so the caller can do sendMsg().then(...).
one of the ways I handled a returning callback to test is as follows:
it('receives successful response', async () => {
nock('https://localhost')
.persist()
.log(console.log)
.post(‘/getData’, (unitData, callback) => {
return true;
})
.delayBody(1000)
.reply(200, {statusCode: 'Some Status'});
const getSomeData = await getResponse(unitData, function callBack(unitData, error, data){
expect(data.statusCode).to.be.equal(200);
}) })
getResponse Function (returning callback):
getResponse(unitData, function callBack(unitData, error, data){
try {
return request.post(unitData, function (err, resp) {
if (!err && resp.statusCode === 200) {
if (resp.body.error) {
return callback(obj, JSON.stringify(resp.body.error), null);
}
return callback(obj, null, resp);
} else {
if (err == null) {
err = { statusCode: resp.statusCode, error: 'Error occured.' };
}
return callback(obj, err, null);
}
});
} catch (err) {
return callback(obj, err, null);
}
}
I've translated my old pure promises funcion to asyn wait function in a backend apollo server, consumed by a react app
All is working well, but i have a doubt about the behaviour magic in async/await function:
here my old code:
const signIn = (args) => {
return new Promise((resolve, reject) => {
return User.findOne({ where: {nick: args.nick }})
.then((user) => {
if (user == null) {
return reject({
code: 'error.loginNick',
message: 'Authentication failed. User not found.'
});
}
if (user.password != args.password) {
console.log('pass incorrect:' + args.password + ' = '+ user.password);
return reject({
code: 'error.loginPassword',
message: 'Wrong password.'
});
}
return resolve(createToken({ id: user.id, nick: user.nick }));
})
.catch(err => {
reject(err);
});
});
};
createToken function:
exports.createToken = payload => (
jwt.sign(payload, config.auth.secret, {
expiresIn: config.auth.expiresIn
})
);
In my singIn function i need to call this last function with:
resolve(createToken.....
if I don't use resolve, my app does not work well because wait a promise. No strange until here, but the behaviour is different in my new rewritten function signIn using async.
my full new async function:
async function signIn(args) {
try {
let err;
if (!args.nick) {
err = {
code: 'nick.empty',
message: 'Nick is empty.'
};
} else if (!args.password) {
err = {
code: 'password.empty',
message: 'You have to provide a password.'
};
}
if (err) {
throw (err);
}
// Find the user
const user = await User.findOne({
where: {
nick: args.nick
}
});
if (user == null) {
err = {
code: 'error.loginNick',
message: 'Authentication failed. User not found.'
};
throw (err);
}
if (user.password != args.password) {
console.log('pass incorrect:' + args.password + ' = ' + user.password);
err = {
code: 'error.loginPassword',
message: 'Wrong password.'
};
throw (err);
}
//return Promise.resolve(createToken({ id: user.id, nick: user.nick })); <-- workin ok too
return createToken({
id: user.id,
nick: user.nick
});
} catch (err) {
throw (err);
}
}
export {
Customer,
Tour,
User,
Auth,
signIn
};
If i use these two alternatives, boths works well ! why ? ok with Promise I understand well that it's works, but wihtout promises is working ok too, not same behavoiur like my old promises function
alternative 1 without promise:
return createToken({ id: user.id, nick: user.nick });
why don't need here promises?, the twhrow if there error go where?
alternative 2 with promise:
return Promise.resolve(createToken({ id: user.id, nick: user.nick }));
the twhrow if there error go where?
this last alternative is anti Pattern ?
I've turned my hand from game dev to writing a supporting back-end. Thus far everything seems to work out but when i got to writing a friend system i ran into this function flow which seemed very dirty to me. And i'm certain i'm just hacking it together at this point. Any node.js wizards about to tell me how I can improve this?
Fairly certain i should be caching player lookups in Redis as well.
acceptfriend: function(req, res){
//Find our user
User.findById( req.decoded._id, function(err, user){
//error occured
if(err){
return res.status(401).send(err);
}
//no user found
if(!user){
return res.status(401).json({
succes: false,
message: 'no user found with that id'
} );
}
//Does the request exist?
if( !_.any( user.social.friendRequests, {id: req.params.id} ) ){
return res.status(401).json( {
succes: false,
message: 'friend request not found'
} );
}
//find the user that belongs to the request
User.findById( req.params.id, function(err, friend){
//error occured
if(err){
return res.send(err);
}
//user doesnt exist
if(!friend){
return res.status(401).json({
succes: false,
message: 'no user found with that id'
} );
}
//Pull the request from the friendRequests array
user.social.friendRequests.pull( req.params.id );
//Add the friend
user.social.friends.addToSet( {
user_id: friend._id,
name: friend.username,
corp: 'n/a'
} );
//Add the user to the friends list as well
friend.social.friends.addToSet({
user_id: user._id,
name: user.username,
corp: 'n/a'
});
//save the docs
user.save();
friend.save();
} );
//return success
return res.status(200).json({
success: true,
message: 'friend succesfully added'
});
} );
}
1- First of all, you have a big function. You have to split it into some functions. Doing this you gain the possibility to test them with any testing framework.
2- Delegate the handle of error responses to the controller.
from -> return res.status(401).send(err);
to (with Promises)-> deferred.reject(err);
to (normal way) -> throw new Error(err);
3- You can use Promises to manage the asynchronous behaviour of node to clear the code.
I created an example, maybe is not working at first time, feel free to fix the incorrent references. The User ref, the 'acceptfriend' method...
Gist: https://gist.github.com/aitoraznar/b7099ad88ead0cdab256
var Promise = require('bluebird');
var _ = require('lodash');
//var User = app.models.User;
var ERRORS = {
userNotFoundError: {
code: 401,
success: false,
message: 'no user found with that id'
},
friendRequestNotFoundError: {
code: 401,
success: false,
message: 'friend request not found'
},
friendNotFoundError: {
code: 401,
success: false,
message: 'no friend found with that id'
}
}
var SUCCESS_MESSAGES= {
friendAddedSuccessfully: {
success: true,
message: 'friend succesfully added'
}
};
var userDAO = {
/*
*
*/
getUserById: function(id) {
var deferred = Promise.pending();
User.findById(id, function(err, user) {
//error occured
if (err) {
err.code = 401;
return deferred.reject(err);
}
//no user found
if (!user) {
return deferred.reject(ERRORS.userNotFoundError);
}
deferred.resolve(user);
});
return deferred.promise;
},
/*
* Does the request exist?
*/
checkFriendRequest: function(user, friendId) {
var deferred = Promise.pending();
if (userDAO.haveFriendRequestFrom(user, friendId)) {
deferred.resolve(user, friendId);
} else {
return deferred.reject(ERRORS.friendRequestNotFoundError);
}
return deferred.promise;
},
/*
*
*/
haveFriendRequestFrom: function(user, friendId) {
return _.any(user.social.friendRequests, {id: friendId });
},
/*
*
*/
getFriend: function(user, friendId) {
var deferred = Promise.pending();
userDAO.getUserById(friendId)
.then(function(friend) {
deferred.resolve(user, friend);
},
function(error) {
if (error === ERRORS.userNotFoundError) {
// Then the error is friend not found
// Override the error
error = ERRORS.friendNotFoundError;
}
return deferred.reject(error);
});
return deferred.promise;
},
/*
*
*/
makeFriendship: function(user, friend) {
var deferred = Promise.pending();
//Pull the request from the friendRequests array
user.social.friendRequests.pull(friend._id);
//Add the friend
user.social.friends.addToSet( {
user_id: friend._id,
name: friend.username,
corp: 'n/a'
} );
//Add the user to the friends list as well
friend.social.friends.addToSet({
user_id: user._id,
name: user.username,
corp: 'n/a'
});
//save the docs
user.save();
friend.save();
// Return the new friendship
var friendship = {
user: user,
friend:friend
};
deferred.resolve(friendship);
return deferred.promise;
},
/*
*
*/
friendRequestError: function(err) {
var deferred = Promise.pending();
// Propagate de error
deferred.reject(err);
return deferred.promise;
},
/*
*
*/
friendRequest: function(userId, friendId) {
var deferred = Promise.pending();
// Get user by ID
userDAO.getUserById(userId)
// Check if the user is able to add the friend
.then(userDAO.checkFriendRequest, userDAO.friendRequestError)
// Get the friend to add
.then(userDAO.getFriend, userDAO.friendRequestError)
// Make the friendship
.then(userDAO.makeFriendship, userDAO.friendRequestError)
// Response to the controller
.then(
function(friendship) {
// Resolve with new friendship
// This goes to 'success' function in controller
deferred.resolve(friendship);
}, function(error) {
// This goes to 'error' function in controller
deferred.reject(error);
})
return deferred.promise;
}
};
// Controller
var acceptfriend = function(req, res, next) {
var userId = req.decoded._id;
var friendId = req.params.id;
userDAO.friendRequest(userId, friendId)
.then(function(friendRequest) {
console.log('---> SUCCESS');
//return success
return res.status(200)
.json(SUCCESS_MESSAGES.friendAddedSuccessfully);
}, function(error) {
console.error('---> ERROR', error);
return res.status(error.code).json(error);
});
}
4- Create database indexes in the collection/table
Regards,
Aitor
I have a little issue trying to perform a get in my app.
first this is the error in the browser console
GET http://localhost:1337/sports/getChecked/12787fb1e00a01337f0508ca47223d15
401 (Unauthorized)
code.min.js:875 Object {err: "JSON request needed"}
code.min.js:543 Error {stack: (...)}
code.min.js:967 ["3042", "59321H", "5932"]
code.min.js:690 ["3042", "59321H", "5932"]
I am doing a post and in the post everything is fine so far, the issue is in the get, I am working with Json web tokens (JWT)...
these are the routes for post and get
module.exports.routes = {
'post /sports/checked': 'SetSportsController.setCheck',
'get /sports/getChecked/:user': 'SetSportsController.retrieveSetCheck'
};
here the policies
module.exports.policies = {
setCheck: ['jwtAuth', 'sanitizerPolicy', 'headersPolicy', 'sessionKiller'],
retrieveSetCheck: ['jwtAuth', 'sanitizerPolicy', 'sessionKiller']
};
get on SetSportController.js
retrieveSetCheck: function(req, res) {
if (req.params) {
SportSelectionService.getSportChecked(req.params).then(function(sportChecked) {
console.log(sportChecked.sport);
res.json(200, sportChecked);
}, function(err) {
res.json(400, err);
});
}else {
res.json(400, {error: 'Error retrieving Sports'});
}
}
SportSelectionService.js
getSportChecked: function(params) {
var Promise = require('bluebird');
return new Promise(function(fullfill, reject) {
console.time('sportChecked_findOne');
SportSelection.find({
user: params.user
}).exec(function(err, sportChecked) {
console.timeEnd('sportChecked_findOne');
if (err) {
reject(new Error('Error finding favorite leagues'));
console.error(err);
}else {
if (sportChecked) {
fullfill(sportChecked.sport);
}else {
console.time('sportChecked_create');
SportSelection.create({
sport: [],
user: params.user
}).exec(function(err, created) {
console.timeEnd('sportChecked_create');
console.log(err);
console.log(created);
if (err) {
reject(new Error('Error on sportChecked'));
}else {
fullfill(created);
fullfill(created.sport);
}
});
}
}
});
});
}
this is the FRONT-END part
<ion-item ng-click="toggleSportSelection(sport)">
{{:: sport.name}}
</ion-item>
controller.js
SportsFactory.getSportChecked(customer).then(function(sportChecked) {
console.log(sportChecked);
_.each(sports, function(sport) {
var sportIds = _.pluck(sport, 'id'),
intersectedSports = _.intersection(sportIds, sportChecked),
checkedSportObjects = _.filter(sport, function(sportObj) {
return _.includes(intersectedSports, sportObj.sportIds);
});
_.each(checkedSportObjects, function(sport) {
$scope.sportObj.push(sport);
});
});
});
$scope.toggleSportSelection = function(sport) {
var params = {};
params.user = $scope.customer.customer;
params.sport = sport.id;
sport.checked = !sport.checked;
SportsFactory.setSportChecked(params);
};
service.js
getSportChecked: function(customer) {
var defer = $q.defer(),
user,
rejection = function(err) {
console.log(err);
defer.reject(err);
};
LocalForageFactory.retrieve(CONSTANT_VARS.LOCALFORAGE_SPORTS_CHECKED)
.then(function(sportChecked) {
user = customer.customer;
if (!_.isNull(sportChecked)) {
defer.resolve(sportChecked);
}else {
$http.get(CONSTANT_VARS.BACKEND_URL + '/sports/getChecked/' + user)
.success(function(sportChecked) {
LocalForageFactory.set(CONSTANT_VARS.LOCALFORAGE_FAVORITE_LEAGUES, sportChecked);
defer.resolve(sportChecked);
})
.error(rejection);
}
}, rejection);
return defer.promise;
}
looking on the files project I got this file named headersPolicy.js which looks like this
module.exports = function(req, res, next) {
var _ = require('lodash');
if (!_.isNull(req.headers)) {
/* This API only accepts JSON, we could send 406 response, but we don't want
* to give any kind of clues ;)
*/
if (!req.is('json')) {
return res.json(401, {err: 'JSON request needed'});
}
next();
}else {
/*
* No headers, wrong request
* */
return res.json(401, {err: 'Headers not present'});
}
};
so where is this error coming from ? what am I doing wrong ?
The middleware below is checking (presumably) whether the Content-Type header is set to 'application/json'. Since you're sending a GET and not a POST request, that header is most likely not application/json.
if (!req.is('json')) {
return res.json(401, {err: 'JSON request needed'});
}