I am using Node.js (noob in BDD). In my controller I have a function like so:
var getUser = function(username, done) {
console.log('prints');
User.findOne({
'local.username': username
}, function (err, user) {
console.log('doesn"t print');
if (err) {
return done('Oops, server error!', null);
} else {
return done(null, user);
}
});
};
I am using Mocha and described a test block like so:
describe('can be created only using a web interface', function () {
describe('The user name should:', function () {
it("be a valid email", function () {
assert(common.isValidEmail(fakeuser.local.username));
});
it("not already exist in the database", function () {
userController.getUser(fakeuser.local.username, function (err, user) {
log.info('The user name is', user);
//I would like to assert here. But user is always undefined.
}());
});
});
});
It seems like User.findOne is not working the way I expect it to since the test passes no matter what and the code inside doesn't even get executed. What am I missing?
NB: Calling the same function in other parts of the code works just fine.
EDIT
The project is accessible here: https://github.com/attosol/nodeseed
The documentation (and project) is far from complete. Just do an npm install, and npm start followed by starting up your MongoDB (it will use the DB - nodeseed). Just signup using any valid email and find the activation URL in your log files.
You'll have to write something to compare against
it("not already exist in the database", function(done){
var wasCalled = false;
userController.getUser('my#email.com', function(err, user){
console.log('doesn"t print');
wasCalled = true;
});
expect(wasCalled).toBe(true);
});
Related
I've made a class in which the constructor is being declared mostly by a mySQL query that looks like that:
constructor(username) {
this.mysql = require('mysql');
// create a connection variable with the required details
this.con = this.mysql.createConnection({
host: "localhost", // ip address of server running mysql
user: "root", // user name to your mysql database
password: "", // corresponding password
database: "db" // use the specified database
});
this.username = username;
this._password = "";
this.con.query("SELECT * FROM users WHERE username = ?", this.username, function (err, result, fields) {
if (err) throw err;
this._password = result[0].password;
});
}
get password() {
return this._password;
}
The issue is , when I'm declaring the class like that:
const user= require("./user.js");
let bot = new user("user1");
console.log(user.password();
The code first accessing the get and getting undefined , and only after the the query is done.
How can I fix it?
You can't make a constructor asynchronous, that's not supported by Javascript.
What you can do is create an asynchronous createUser function that returns a user, and modify the constructor of your User class to take the data returned from the database.
Something like:
class User {
// Synchronous constructor
constructor(mysqlData) {
this._password = mysqlData.password;
}
}
// Asynchronous create user function
function createUser(name, callback) {
con.query('your query', [], function (err, result) {
if (err) return callback(err); // Traditional error-first callback
const user = new User(result[0]);
callback(null, user);
}
}
// Create the user and then do something once the user is created
createUser('user1', function (err, user) {
if (err) throw err;
console.log(user.password());
});
Unrelated tip: look into Promises and async/await, they are so much nicer to use than callbacks. Then your createUser function would look something like this (notice the await keyword instead of a callback for the query):
async function createUser(name) {
const result = await con.query('your query', []);
const user = new User(result[0]);
return user;
}
And then you can do const user = await createUser('user1'); from another async function (you can only use await inside of an async function).
Keep in mind that for this to work, you need:
A runtime that supports async/await (Node 7.6+ or a modern browser), or Babel to transpile the code so it can run in older runtimes & browsers.
A mysql client that supports promises (there are wrappers on npm for most packages).
I'm creating a simple Messenger bot, using unofficial Facebook Chat API (https://github.com/Schmavery/facebook-chat-api) and Node.js.
For now, I'm working on sending messages to specific users, on a specific time. Here's part of my code:
if(msgdate.getTime() <= currdate.getTime()){
console.log(alarms[i].message);
// output: test
api.getUserID(alarms[i].user, (err, users) => {
if(err) return console.error(err);
api.sendMessage(alarms[i].message, users[0].userID);
// TypeError: Cannot read property 'message' of undefined
});
}
And, my question is: how could I pass alarms array to this callback, so I'd be able to send message to a specific user?
Looks like that you change i variable somewhere, and because of that when callback is called alarms[i] is undefined. You need to store alarms[i] in a new variable and use it in callback:
let alarm = alarms[i];
api.getUserID(alarm.user, (err, users) => {
if(err) {
return console.error(err);
}
api.sendMessage(alarm.message, users[0].userID);
});
Appear that you are using for loop outside. Try pass your var that way:
var alarm = alarm[i];
(function (alarm) {
if(msgdate.getTime() <= currdate.getTime()){
console.log(alarm.message);
// output: test
api.getUserID(alarm.user, (err, users) => {
if(err) return console.error(err);
api.sendMessage(alarm.message, users[0].userID);
// TypeError: Cannot read property 'message' of undefined
});
})(alarm);
Maybe you need to pass other vars too. Just put a comma and other var, example:
(function (alarm, user){
// your code here
})(alarm, user);
I am using the imap-simple NPM package to check emails, and I am having trouble getting the insert to work properly.
I have already read through this page: https://guide.meteor.com/using-npm-packages.html#async-callbacks - and I have tried the suggestions but none of them are working!
I've also simplified the code a bit just to try to get it working, but still have no luck.
The problem should be very easy to reproduce - meteor npm install imap-simple, throw the above code on the server, add some email credentials, and call the method.
Here is my code:
var imaps = require('imap-simple');
var config = {
imap: {
user: '<removed>',
password: '<removed>',
host: 'imap.gmail.com',
port: 993,
tls: true,
authTimeout: 3000
}
};
Meteor.methods({
api_connectEmail: function () {
console.log('Received call to connect email');
imaps.connect(config).then(function (connection) {
return connection.openBox('INBOX').then(function () {
var searchCriteria = [
'UNSEEN'
];
var fetchOptions = {
bodies: ['HEADER', 'TEXT'],
markSeen: true
};
return connection.search(searchCriteria, fetchOptions).then(function (results) {
results.map(function (res) {
var subject = res.parts.filter(function (part) {return part.which === 'HEADER';})[0].body.subject[0];
console.log("Subject: " + subject);
// insert
var attributes = {
subject: subject
};
console.log("Attempting to insert to collection...");
var newData = TempEmailCollection.insert(attributes);
console.log("New Database Entry ID: " + newData);
});
});
});
})
}
});
The console.log with the subject is working. The insert is not working. No error, no console.log post insert, nothing.
I've tried both strategies recommended in the guide, neither work.
The problem is that you are calling a Meteor function inside asynchronously called Promise handlers.
However, all Meteor functions that are called on the server have to run in a fiber.
Meteor actually throws an error in this case but you are ignoring it because you haven't specified catch functions for the Promises.
Consider the following simplified example (it just connects to the server and tries to insert a new document):
import { Meteor } from 'meteor/meteor';
import imaps from 'imap-simple';
const Storage = new Mongo.Collection('storage');
const config = {
imap: {
…
}
};
Meteor.methods({
connect() {
console.log('Method called');
imaps.connect(config).then(function(connection) {
console.log('Connected');
Storage.insert({
value: 'success'
});
console.log('Document inserted');
})
.catch(function(err) {
console.error(err);
});
}
});
The following message will arrive in the catch function:
[Error: Meteor code must always run within a Fiber. Try wrapping callbacks that you pass to non-Meteor libraries with Meteor.bindEnvironment.]
You could do something like this to wrap the insert call:
Meteor.methods({
connect() {
console.log('Method called');
const insert = Meteor.bindEnvironment(function() {
Storage.insert({
value: 'success'
});
});
imaps.connect(config).then(function(connection) {
console.log('Connected');
insert();
console.log('Document inserted');
})
.catch(function(err) {
console.error(err);
});
}
});
Then the document will be inserted as expected.
postRegistrationHandler: function (account, req, res, next) {
console.log('postRegistrationHandler activated');
account.getCustomData(function(err, data) {
if (err) {
console.log(err.toString, "error string");
return next(err);
} else {
data.mongo_id = userCreationCtrl(account);
data.save();
next();
}
});
},
This function almost works properly, but the line:
data.save();
runs before the previous line finishes which means that the data I want to save isn't present at the appropriate time.
data.mongo_id = userCreationCtrl(account);
This line calls a function that creates a mongoDB document with information in the account object and then returns the _id (which is what I am trying to save.
I thought maybe using a .then() would help but that seems to be unavailable here for some reason. If anyone sees something I'm missing, that would be quite helpful. Thank you!
Here is the userCreationCtrl file as requested:
var UserSchema = require('./../models/UserModel.js');
var createNewUser = function (account, res, next){
// We will return mongoId after it is created by submitting a newUser
var mongoId = "";
// Save StormpathID (last 22 characters of account.href property)
var newStormpathId = account.href.slice(account.href.length - 22);
console.log('stormpath ID:', newStormpathId, 'just registered!');
console.log(account);
// Create new user from model by recycling info from the Stormpath registration form and include the stormpathId as well.
var newUser = new UserSchema({
stormpathId: newStormpathId,
firstName: account.givenName,
lastName: account.surname,
email: account.email,
street: account.street,
city: account.city,
zip: account.zip
});
// This saves the user we just created in MongoDB
newUser.save(function(err, result){
console.log(result);
if (err) {
console.error(err);
}
else {
console.log("User created in MongoDB, attempting to return mongoDB _id to stormpath customData");
// Keep track of the new user's mongo _id so we can return it to the previous function and save it as Stormpath custom data.
mongoId = result._id;
console.log(mongoId, "mongoid");
return result._id;
}
});
};
module.exports = createNewUser;
You have userCreationCtrl expecting 3 arguments, account, res, and next. next is the callback that should be called after the user is created so instead of return result._id you should call next like so:
// inside of createNewUser()
newUser.save(function(err, result){
console.log(result);
if (err) {
console.error(err);
}
else {
console.log("User created in MongoDB, attempting to return mongoDB _id to stormpath customData");
// Keep track of the new user's mongo _id so we can return it to the previous function and save it as Stormpath custom data.
mongoId = result._id;
console.log(mongoId, "mongoid");
// IMPORTANT change to make it all work...
// get rid of return result._id because its not doing anything
// pass the value to your callback function instead of returning the value
next(null, result._id);
}
});
then calling code in postRegistrationHandler should look like this:
account.getCustomData(function(err, data) {
if (err) {
console.log(err.toString, "error string");
return next(err);
} else {
// pass in a callback as the 3rd parameter that will be called by newUser.save() when its finished
userCreationCtrl(account, null, function(err, resultId) {
data.save();
next();
});
}
});
I am new to NodeJS and coming from a PHP environment I am trying to figure out how to work with multiple callbacks. I do understand the basics about callback and I think it does make sens when writing modules. My problem is when comes the time to use those modules how to organize all the callbacks. Below is my implementation a request reset password controller method (I am using SailsJS). This is a first draft of my code. It was mainly to test a method of organizing callbacks. What do you guys think of this structure ? Is there a better way do it?
var _ = require('lodash');
var moment = require('moment');
var mailer = require("../../services/Mailer");
var crypto = require('../../services/crypto');
var forms = require("forms"),
fields = forms.fields,
validators = forms.validators;
module.exports = {
// Request reset user password: submit form and send email
request_process : function(req, res, next) {
var form = createForm();
form.handle(req, {
// there is a request and the form is valid
// form.data contains the submitted data
success: function (form) {
var user = null;
var username = form.data.username;
User.findOne({'username' : username}, foundUser);
function foundUser( err, _user){
if(err)
res.send(500, 'User not found');
user = _user;
if user.isPasswordRequestNonExpired()
return res.view('user/resetting/passwordAlreadyRequested');
if !user.passwordRequestToken
user.passwordRequestToken = crypto.randomToken();
renderEmail(null, user);
}
function renderEmail(err, user){
res.render('email/resetting_check_email', {'user': user, }, sendEmail );
}
function sendEmail(err, template){
if(err)
return res.send(500, "Problem with sending email");
Mailer.send( user, "Reset Password", template, sentEmail);
}
function sentEmail(err, response){
if(err)
return res.send(500, "Error sending email");
user.passwordRequestedAt = moment().format();
user.save(finish);
}
function finish(err){
if(err)
return res.send(500);
res.view();
}
},
// the data in the request didn't validate,
// calling form.toHTML() again will render the error messages
error: function (form) {
console.log("registration error", form);
res.locals.form = form;
return res.render('user/registration/register');
},
// there was no form data in the request
empty: function (form) {
console.log("registration empty", form);
res.locals.form = form;
return res.render('user/registration/register');
}
},
// Tell the user to check his email provider
check_email : function(req, res, next) {
// if empty req.params.email
// redirect request view
// res.view('check_email.ejs')
},
// Reset user password
reset : function(req, res, next){
// find userByPasswordToken
// if !user
// res.view ('invalid or expired "confirmation token".')
// user.update password
// res.view('reset.ejs');
},
Node.js callback basics:
Most of the functions (Node and its Libraries (called modules)), are of asynchronous (async) nature.
These functions have a common signature with callback as the last argument: function(arguments.....callback).
The callback is just another JavaScript function. ( yes, in Javascript, functions can be passed around as arguments to other functions). Node.js typical callbacks have a signature with first argument as error (if one happened): callback(err,outputs......).
example: first argument is a string, second an object (defined inline) and the last is a function (defined inline).
doSomeWork('running',{doFast:true,repeat:20}, function(err,result){
if(err){
console.log('ohnoes!);
} else {
console.log('all done : %s',result);
}
});
which is equivalent to:
var type = 'running';
var options = {doFast:true,repeat:20};
var callback = function(err,result){
if(err){
console.log('ohnoes!);
} else {
console.log('all done : %s',result);
}
};
doSomeWork(type,options,callback);
So the basic contract here is give a function its arguments and pass a callback to be invoked, when it is done. The passed call back will be invoked some where in future when there is something to return, error or the results.
Multiple nested callbacks are generally less readable and complex:
function uploadAll(path,callback){
listFiles(path,function(err,files){
if(err){
callback(err);
}else{
var uploaded = [];
var error;
for(var i = 0 ; i < files.length; i++){
uploadFile(files[i],function(err,url){
if(err){
error = err;
break;
}else{
uploaded.push(url);
}
});
}
callback(error,uploaded);
}
});
};
But fortunately there are modules like async that help organize callbacks:
function uploadAll(path,callback){
async.waterfall(
[
function(cb){
listFiles(path,cb);
},
function(files,cb){
async.map(files,uploadFile,cb);
}
],callback);
}
Furthermore, there is a Promises pattern as well. Future versions support generators which provide many new async patterns.
you can use async or q to manage the callback pyramids