in bluebird / bookshelf.js what does tap function do - node.js

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.

Related

.then statements not executing sequentially

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.

How to handle promise returning with Mongoose using Bluebird?

I have an Account schema, defined with Mongoose, I've setup promises with Bluebird:
var mongoose = require('mongoose');
mongoose.Promise = require('bluebird');
I've designed a model method for such schema:
accountSchema.methods.validPassword = function(password) {
return bcrypt.compareSync(password, this.password);
}
So I got a method which will try to find a user and check for password match:
function login (email,password) {
return Account.findOne({email: email}).then(function (user) {
console.log(user);
user["match"] = user.validPassword(password);
console.log(user);
return user.validPassword(password);
});
}
What's really weird is that the second console.log won't show up any match property for the object.
Here my intention is to return a promise of finding a user and check for password match, however when I invoke login:
login("email","password").then(function(user){...})
User doesn't have a match property, how could I achieve it?
You cannot do both a return prior to invoking the Promise call : return Account.xxxxx AND do a .then() .... its an either or ... I give you both alternatives. Version A we process resultset local to login function :
function login (email,password) {
// notice I no longer have return Account.xxxx
Account.findOne({email: email}) // Account.findOne returns a Promise
.then(function (user) {
if (user) {
user.match = user.validPassword(password);
// execute some callback here or return new Promise for follow-on logic
} else {
// document not found deal with this
}
}).catch(function(err) {
// handle error
});
}
here the caller does :
login("email","password") // needs either a cb or promise
.then(function(userProcessed) { ...
}).
... whereas in Version B we relegate processing to caller to do the .then() logic :
function login (email,password) {
return Account.findOne({email: email});
}
so in caller we have :
login("email","password").then(function(userNotProcessed){...})
Once you have the result set from the findOne, perform some validation on the user , avoid assuming it was found.
Also since Promise is now in ES6, you can use the built in Promise implementation
mongoose.Promise = global.Promise;
Take note that a findOne returns a document, whereas doing a find always gives you an array of 0 or more document(s)
login(email,password){
return new Promise(function(resolve,reject){
Account.findOne({email: email}).then(function (user) {
user.match = user.validPassword(password);
resolve(user)
});
})
}

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
})

Using Mongoose promises to do find and update

I'm trying to using the Mongoose Promises to have a cleaner code (see nested functions).
Specifically, I'm trying to build something like this:
Model.findOne({_id: req.params.id, client: req.credentials.clientId}).exec()
.then(function(resource){
if (!resource) {
throw new restify.ResourceNotFoundError();
}
return resource;
})
.then(function(resource) {
resource.name = req.body.name;
return resource.save; <-- not correct!
})
.then(null, function(err) {
//handle errors here
});
So, in one of the promises I would need to save my model. As of the latest stable release, Model.save() does not return a promise (bug is here).
To use the classical save method, I could use this:
//..before as above
.then(function(resource) {
resource.name = req.body.name;
resource.save(function(err) {
if (err)
throw new Error();
//how do I return OK to the parent promise?
});
})
But as also commented in the code, how do I return to the holding promise the return value of the save callback (which runs async)?
Is there a better way?
(btw, findOneAndUpdate is a no-go solution for my case)
One way of doing it would be to wrap the .save code in your own method which returns a promise. You'll need a promise library, like RSVP or Q. I'll write it in RSVP, but you should get the idea.
var save = function(resource) {
return new RSVP.Promise(function(resolve, reject) {
resource.save(function(err, resource) {
if (err) return reject(err);
resolve(resource);
});
});
}
Then in your calling code:
// ...
.then(function(resource) {
resource.name = req.body.name;
return save(resource);
})
.then(function(resource) {
// Whatever
})
.catch(function(err) {
// handle errors here
});
The other way of doing it would be to nodeify the save method, but I'd do it they way I've detailed above.

handling callbacks in node

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);
});

Resources