I'm still learning node programming....I'm building a web app using express and want to wrap my head around event-based non-blocking I/O with respect to nesting callbacks from other functions inside other callbacks.
Here is one issue that I am trying to understand with respect to using callbacks everywhere:
I cannot just do this in most cases (crypo will allow this method to work sync so this example is ok):
user.reset_password_token = require('crypto').randomBytes(32).toString('hex');
I was having to go about doing this before I saw that the above example works:
User.findOne({ email: req.body.username }, function(err, user) {
crypto.randomBytes(256, function(ex, buf) {
if (ex) throw ex;
user.reset_password_token = buf.toString('hex');
});
user.save(); // can I do this here?
//will user.reset_password_token be set here??
// Or do I need to put all this code into the randomBytes callback...
//Can I continue programming the .findOne() callback here
// with the expectation that
//user.reset_password_token is set?
//Or am I out of bounds...for the crypto callback to have been called reliably.
});
If I call user.save() after the randomBytes code (not inside it's callback) will the token always be set?
//will user.reset_password_token be set here?
No. In the example you have, the call to crypto is done asynchronously which means that execution will not stop to let that call finish and will instead continue executing code within your findOne method.
User.findOne({ email: req.body.username }, function(err, user) {
crypto.randomBytes(256, function(ex, buf) {
if (ex) throw ex;
user.reset_password_token = buf.toString('hex');
// only within this scope will user.reset_password_token be
// set as expected
user.save();
});
// undefined since this code is called before the crypto call completes
// (at least when you call crypto async like you have)
console.log(user.reset_password_token);
});
Related
I have an application using Node.js/Express. Within this code I have the following promise designed to check if an email already exists in my (PostGres) database:
//queries.js
const checkEmail = function(mail) {
return new Promise(function(resolve, reject) {
pool.query('SELECT * FROM clients WHERE email = $1', [mail], function(error, results) {
if (error) {
reject(new Error('Client email NOT LOCATED in database!'));
} else {
resolve(results.rows[0]);
}
}) //pool.query
}); //new promise
}
In my 'main (server.js)' script file, I have a route which is called upon submission of a 'signup' form. When the post to this route is processed...I run the script above to check if the passed email address is already located in the database, along with various other 'hashing' routines:
My code is as follows:
//server.js
const db = require('./queries');
const traffic = require('./traffic');
const shortid = require('shortid');
...
app.post('/_join_up', function(req, res) {
if (!req.body) {
console.log('ERROR: req.body has NOT been returned...');
return res.sendStatus(400)
}
var newHash, newName;
var client = req.body.client_email;
var creds = req.body.client_pword;
var newToken = shortid.generate();
var firstname = req.body.client_alias;
db.sanitation(client, creds, firstname).then(
function(direction) {
console.log('USER-SUPPLIED DATA HAS PASSED INSPECTION');
}
).then(
db.checkEmail(client).then(
function(foundUser) {
console.log('HEY THERE IS ALREADY A USER WITH THAT EMAIL!', foundUser);
},
function(error) {
console.log('USER EMAIL NOT CURRENTLY IN DATABASE...THEREFORE IT IS OK...');
}
)).then(
traffic.hashPassword(creds).then(
function(hashedPassword) {
console.log('PASSWORD HASHED');
newHash = hashedPassword;
},
function(error) {
console.log('UNABLE TO HASH PASSWORD...' + error);
}
)).then(
traffic.hashUsername(firstname).then(
function(hashedName) {
console.log('NAME HASHED');
newName = hashedName;
},
function(error) {
console.log('UNABLE TO HASH NAME...' + error);
}
)).then(
db.createUser(client, newName, newHash, newToken).then(
function(data) {
console.log('REGISTERED A NEW CLIENT JOIN...!!!');
res.redirect('/landing'); //route to 'landing' page...
},
function(error) {
console.log('UNABLE TO CREATE NEW USER...' + error);
}
))
.catch(function(error) {
console.log('THERE WAS AN ERROR IN THE SEQUENTIAL PROCESSING OF THE USER-SUPPLIED INFORMATION...' + error);
res.redirect('/');
});
}); //POST '_join_up' is used to register NEW clients...
My issue is the '.then' statements do not appear to run sequentially. I was under the impression such commands only run one after the other...with each running only when the previous has completed. This is based upon the logs which show the readout of the 'console.log' statements:
USER-SUPPLIED DATA HAS PASSED INSPECTION
PASSWORD HASHED
NAME HASHED
UNABLE TO CREATE NEW USER...Error: Unable to create new CLIENT JOIN!
USER EMAIL NOT CURRENTLY IN DATABASE...THEREFORE IT IS OK...
As mentioned previously, I am under the impression the '.then' statements should run synchronously, therefore the last statement ("USER EMAIL NOT CURRENTLY IN DATABASE...THEREFORE IT IS OK...") should in fact be after the first...before the "PASSWORD HASHED" according to the layout of the '.then' statements. Is this normal behavior...or do I have an error in my code?
Sorry for my confusion however I find '.then' statements and promises to be somewhat confusing for some reason. I thank you in advance.
TLDR - You must pass a function reference to .then() so the promise infrastructure can call that function later. You are not doing that in several places in your code.
A more specific example from your code:
You have several structures like this:
.then(db.createUser().then())
This is incorrect. This tells the interpreter to run db.createUser() immediately and pass its return result (a promise) to .then(). .then() will completely IGNORE anything you pass is that is not a function reference and your promises will not be properly chained.
Instead, you must pass a function reference to .then() something like this (not sure what execution logic you actually want):
.then(() => { return db.createUser.then()})
Then main point here is that if you're going to sequence asynchronous operations, then you must chain their promises which means you must not execute the 2nd until the first calls the function you pass to .then(). You weren't passing a function to .then(), you were executing a function immediately and then passing a promise to .then(p) which was completely ignored by .then() and your function was executed before the parent promise resolved.
FYI, sequencing a bunch of asynchronous operations (which it appears you are trying to do here) can take advantage of await instead of .then() and end up with much simpler looking code.
I'm new to node and I'm trying to save something to mongo via mongoose.
I want to return false if save has an error(eg. there is a user with the current name).
The problem is the order of the console.logs 1:2:6:7:3:4:5
is there a way I can call adduser synchronously?
module.exports.addUser = function(username,password){
console.log(1);
var newUser = User({
username:username,
password:password
});
console.log(2);
newUser.save(function(err,prod,numeffect){
if (err){
console.log(err);
}
if (numeffect === 1){
console.log(3);
console.log("num effect = "+numeffect);
}
console.log(4);
console.log(prod);
}).then(function(doc){console.log(5)});
console.log(6);
}
mongoose.addUser(req.body.username,req.body.password);
console.log(7);
You should not do them synchronously, or you'll block your whole Node thread.
Instead, what you should be doing is to return a promise, or a simple function with callback. (like mongoose saving one, notice that mongoose supports both)
You can do return newUser.save()
Then you'd run it like addUser.then(). Also, you can drop your module.exports = function() and do module.exports your function with callback or promise.
NodeJs is single threaded process, and you should not block it with synchronous calls.
You should pass a callback function to perform any actions when you receive a response from mongodb.
That's a significant switch from call and block to callback, when coming into NodeJs.
What does the bookshelf.js tap function do. I didn't find any entry in documentation
return new Library({name: 'Old Books'})
.save(null, {transacting: t})
.tap(function(model) {
//code here
}
http://bookshelfjs.org/#Bookshelf-subsection-methods
Bookshelf uses Bluebird for their promises, and I believe .tap() is one of their specific Promise methods. Looks like it allows you to essentially call a .then() without altering the value being passed through the chain.
http://bluebirdjs.com/docs/api/tap.html
Here is an example of the difference between Promise#tap() and Promise#then(). Note that Promise#tap() is not standard, and is Bluebird-specific.
var Promise = require('bluebird');
function getUser() {
return new Promise(function(resolve, reject) {
var user = {
_id: 12345,
username: 'test',
email: 'test#test.com'
};
resolve(user);
});
}
getUser()
.then(function(user) {
// do something with `user`
console.log('user in then #1:', user);
// make sure we return `user` from `#then()`,
// so it becomes available to the next promise method
return user;
})
.tap(function(user) {
console.log('user in tap:', user);
// note that we are NOT returning `user` here,
// because we don't need to with `#tap()`
})
.then(function(user) {
// and that `user` is still available here,
// thanks to using `#tap()`
console.log('user in then #2:', user);
})
.then(function(user) {
// note that `user` here will be `undefined`,
// because we didn't return it from the previous `#then()`
console.log('user in then #3:', user);
});
According to Reg “Raganwald” Braithwaite,
tap is a traditional name borrowed from various Unix shell commands.
It takes a value and returns a function that always returns the value,
but if you pass it a function, it executes the function for
side-effects. [source]
Here is the same question being posed regarding underscore.js.
The gist is this: all tap does is return the object it was passed. However, if it is passed a function, it will execute that function. So, it is useful for debugging or for executing side-effects within an existing chain without altering that chain.
I'm trying to test my REST API endpoint handlers using Mocha and Chai, the application was built using Express and Mongoose. My handlers are mostly of the form:
var handler = function (req, res, next) {
// Process the request, prepare the variables
// Call a Mongoose function
Model.operation({'search': 'items'}, function(err, results) {
// Process the results, send call next(err) if necessary
// Return the object or objects
return res.send(results)
}
}
For example:
auth.getUser = function (req, res, next) {
// Find the requested user
User.findById(req.params.id, function (err, user) {
// If there is an error, cascade down
if (err) {
return next(err);
}
// If the user was not found, return 404
else if (!user) {
return res.status(404).send('The user could not be found');
}
// If the user was found
else {
// Remove the password
user = user.toObject();
delete user.password;
// If the user is not the authenticated user, remove the email
if (!(req.isAuthenticated() && (req.user.username === user.username))) {
delete user.email;
}
// Return the user
return res.send(user);
}
});
};
The problem with this is that the function returns as it calls the Mongoose method and test cases like this:
it('Should create a user', function () {
auth.createUser(request, response);
var data = JSON.parse(response._getData());
data.username.should.equal('some_user');
});
never pass as the function is returning before doing anything. Mongoose is mocked using Mockgoose and the request and response objects are mocked with Express-Mocks-HTTP.
While using superagent and other request libraries is fairly common, I would prefer to test the functions in isolation, instead of testing the whole framework.
Is there a way to make the test wait before evaluating the should statements without changing the code I'm testing to return promises?
You should use an asynchronous version of the test, by providing a function with a done argument to it.
For more details refer to http://mochajs.org/#asynchronous-code.
Since you don't want to modify your code, one way to do that could be by using setTimeout in the test to wait before to call done.
I would try something like this:
it('Should create a user', function (done) {
auth.createUser(request, response);
setTimeout(function(){
var data = JSON.parse(response._getData());
data.username.should.equal('some_user');
done();
}, 1000); // waiting one second to perform the test
});
(There might be better way)
Apparently, express-mocks-http was abandoned a while ago and the new code is under node-mocks-http. Using this new library it is possible to do what I was asking for using events. It's not documented but looking at the code you can figure it out.
When creating the response object you have to pass the EventEmitter object:
var EventEmitter = require('events').EventEmitter;
var response = NodeMocks.createResponse({eventEmitter: EventEmitter});
Then, on the test, you add a listener to the event 'end' or 'send' as both of them are triggered when the call to res.send. 'end' covers more than 'send', in case you have calls other than res.send (for example, res.status(404).end().
The test would look something like this:
it('Should return the user after creation', function (done) {
auth.createUser(request, response);
response.on('send', function () {
var data = response._getData();
data.username.should.equal('someone');
data.email.should.equal('asdf2#asdf.com');
done();
});
});
I'm using a save middleware in Mongoose to create a log of activity in the DB whenever some action is taken. Something like
UserSchema.post("save", function (doc) {
mongoose.model("Activity").create({activity: "User created: " + doc._id});
});
This appears to work fine, but the problem is that I can't test it because there is no way to pass a callback to post (which probably would not make sense). I test this out using mocha with:
User.create({name: "foo"}, function (err, user) {
Activity.find().exec(function (err, act) {
act[0].activity.should.match(new RegExp(user._id));
done();
});
});
The problem is that the Activity.create apparently does not finish before .find is called. I can get around this by wrapping .find in setTimeout, but this seems hacky to me. Is there any way to test asynchronous mongoose middleware operations?
Unfortunately, there's not a way to reliably interleave these two asynchronous functions in the way you'd like (as there aren't threads, you can't "pause" execution). They can complete in an inconsistent order, which leaves you to solutions like a timeout.
I'd suggest you wire up an event handler to the Activity class so that when an Activity is written/fails, it looks at a list of queued (hashed?) Activities that should be logged. So, when an activity is created, add to list ("onactivitycreated"). Then, it will eventually be written ("onactivitywritten"), compare and remove successes maybe (not sure what makes sense with mocha). When your tests are complete you could see if the list is empty.
You can use util.inherits(Activity, EventEmitter) for example to extend the Activity class with event functionality.
Now, you'll still need to wait/timeout on the list, if there were failures that weren't handled through events, you'd need to handle that too.
Edit -- Ignore the suggestion below as an interesting demo of async that won't work for you. :)
If you'd like to test them, I'd have a look at a library like async where you can execute your code in a series (or waterfall in this case) so that you can first create a User, and then, once it completes, verify that the correct Activity has been recorded. I've used waterfall here so that values can be passed from one task to the next.
async.waterfall([
function(done) {
User.create({name: "foo"}, function (err, user) {
if (err) { done(err); return; }
done(null, user._id); // 2nd param sent to next task as 1st param
});
},
function(id, done) { // got the _id from above
// substitute efficient method for finding
// the corresponding activity document (maybe it's another field?)
Activity.findById(id, function (err, act) {
if (err) { done(err); return; }
if (act) { done(null, true);
done(null, false); // not found?!
});
}
], function(err, result) {
console.log("Success? " + result);
});
Async post-save middleware will apparently be available in Mongoose 4.0.0:
https://github.com/LearnBoost/mongoose/issues/787
https://github.com/LearnBoost/mongoose/issues/2124
For now, you can work around this by monkey-patching the save method on the document so that it supports async post-save middleware. The following code is working for me in a similar scenario to yours.
// put any functions here that you would like to run post-save
var postSave = [
function(next) {
console.log(this._id);
return next();
}
];
var Model = require('mongoose/lib/model');
// monkey patch the save method
FooSchema.methods.save = function(done) {
return Model.prototype.save.call(this, function(err, result) {
if (err) return done(err, result);
// bind the postSave functions to the saved model
var fns = postSave.map(function(f) {return f.bind(result);});
return async.series(fns,
function(err) {done(err, result);}
);
});
};