Use bluebird for mongoose, got ".bind is not a function" - node.js

I use bluebird for mongoose:
const Promise = require("bluebird");
const mongoose = require("mongoose");
mongoose.Promise = Promise;
And I want to use Promise.bind to share variable in the promise chain:
function getAutherOfBook(name)
{
return Book.findOne(
{
name: name
}, "-_id auther")
.then(doc =>
{
return doc.auther;
});
};
function geNationalityOfAuther(name)
{
return Auther.findOne(
{
name: name
}, "-_id nationality")
.then(doc =>
{
return doc.nationality;
});
};
getAutherOfBook("The Kite Runner")
.bind({})
.then(auther =>
{
this.auther = auther;
return geNationalityOfAuther(auther);
})
.then(nationality =>
{
console.log("auther: ", this.auther);
console.log("nationality: ", nationality);
})
.bind()
But I got the error: getAutherOfBook(...).bind is not a function
Maybe bluebird is not working for mongoose?

The problem you are having is that mongoose queries does not return full fledge promises -- directly quoting http://mongoosejs.com/docs/promises.html (v4.7.6)
// A query is not a fully-fledged promise, but it does have a `.then()`.
query.then(function (doc) {
// use doc
});
// `.exec()` gives you a fully-fledged promise
var promise = query.exec();
assert.ok(promise instanceof require('mpromise'));
In other words, the then function is syntax sugar and not a promise which is why the bind and other promise functions does not work.
To make it work, you either wrap it up in a full promise or use the exec function as suggested in the docs

Related

Aync / Await return pending

Why is my Aync Await Function returning an Pending Promise ? i already tried .then statement but it doesn't work, here is my code :
const findData = async () => {
let query = await userSchema.findOne({ _id: research["uploaderID"] });
return query;
};
research["uploaderInfo"] = findData();
console.log(findData());
when i tried to console.log the findData, it just gave me this :
Promise { <pending> }
Promise { <pending> }
and when i tried to check the research object, it was empty, but when i tried to add a console.log(query) inside the findData() function, it gave me the expected result, which mean the query is correct, and this is an issue because of the async / await.
UPDATE
i tried #dai solution to add await when i tried to set my research like this
const findData = async () => {
let query = await userSchema.findOne({ _id: research["uploaderID"] });
return query;
};
async () => {
research["uploaderInfo"] = await findData();
};
when i tried this, any code that i put inside the second nameless async function does not work, i tried to set the object to random string and it still doesn't changing
Read this: https://stackoverflow.com/a/56590390/159145
Short answer: Run NodeJS 14.17 (or any version after 13.3+) with the flag --harmony-top-level-await and you can have this:
// program.js
const findData = async () => {
let query = await userSchema.findOne({ _id: research["uploaderID"] });
return query;
};
research["uploaderInfo"] = await findData();
console.log(research["uploaderInfo"]);
...or even just this (assuming your research and userSchema objects are trivially instantiated):
// program.js
research["uploaderInfo"] = await userSchema.findOne({ _id: research["uploaderID"] });
console.log(research["uploaderInfo"]);
Longer answer without NodeJS flags:
It looks like NodeJS 14.17 doesn't yet support "top-level await" (someone correct me if I'm wrong) but that's not a real problem: it just means you need to wrap all of your original top-level ("root function") code in an async function and invoke it immediately.
Like so (I've named the function entrypoint, though you can use an anonymous function if you like):
// program.js
async function entrypoint() {
const research = ...
const userSchema = ...
const findData = async () => {
let query = await userSchema.findOne({ _id: research["uploaderID"] });
return query;
};
research["uploaderInfo"] = await findData();
console.log(research["uploaderInfo"]);
}
entrypoint();
Note that you can elide and inline findData's function and call userSchema.findOne directly:
// program.js
async function entrypoint() {
const research = ...
const userSchema = ...
research["uploaderInfo"] = await await userSchema.findOne({ _id: research["uploaderID"] });
console.log(research["uploaderInfo"]);
}
entrypoint();

Creating a Mongoose plugin that supports callbacks AND promises

I have a Mongoose plugin that currently only supports callbacks, I plan on possibly publishing it to npmjs, but I first wanted to make sure that it works just like the existing Mongoose functions/methods, which support callbacks and some built in promises, and you can also specify your own promise library.
I wanted to know what the best way was to implement the same functionality in my library, meaning how can I support both callbacks and promises? I found a similar SO thread, but thats specific to bluebird, which even though I like to use, Id like to not assume it will be used. (Also, that article looks like it might be out-dated, as I couldnt find nodeify in the bluebird api docs
I was thinking I could just do some basic logic to see if a function was provided as one of the parameters, if so, then execute the callback, if not, then return a promise... but im sure theres an easier way to do that.
Also, for the Promises, when I do return a promise, should I use the Promise thats inside the Mongoose object thats handed to the plugins? Meaning:
module.exports = Mongoose => {
// Just verifying, should I use this?..
const Promise = Mongoose.Promise
return Mongoose.model( 'Foobar', new Mongoose.Schema({ /* ... */ }) )
}
Update
Regarding the last question, about what Promise object to reference when returning promises, I tried using the Mongoose.Promise as stated above, code below:
module.exports = Mongoose => {
const Promise = Mongoose.Promise
const Schema = Mongoose.Schema
const fooSchema = new Schema({
name: Schema.Types.String
})
fooSchema.statics.blah = function( ) {
// Return Mongoose.Promise
return new Promise( ( res, rej ) => {
res( 'test' )
})
}
return Mongoose.model( 'Foobar', fooSchema )
}
... Which resulted in the error:
/Users/me/project/node_modules/mongoose/lib/ES6Promise.js:21
throw 'Can\'t use ES6 promise with mpromise style constructor';
^
Can't use ES6 promise with mpromise style constructor
So I'm guessing thats not the right way to go... I thought it would be a better idea to return promises using the same promise library Mongoose has configured (Custom, or default..)
I tried to see how Mongoose does it by looking at the findOne function code, but I dont quite see how it returns a promise if theres no callback specified
P.S. Im using Mongoose 4.3.7
Update
Was just tinkering around, but would this be an acceptable method? Or is it bad practice
function tester(foo, cb){
return new Promise( ( res, rej ) => {
const result = 'You said ' + foo
if( typeof cb === 'function' ){
cb( null, result )
res() // Is this needed?
}
else {
res( result )
}
})
}
tester('foo', (err, data) => {
if( err )
console.log('ERROR!',err)
else
console.log('Result:',data)
})
tester('bar')
.then( data => {
console.log('Result:',data)
})
.catch( err => {
console.log('ERROR!',err)
})
just do this
mongoose.Promise = global.Promise
as simple as that
So I mainly didn't want to rely on a specific promise library, incase they were using a different one, but I realized that for the most part, it shouldnt really matter. So I decided to stick with Bluebird, and use the asCallback method
Heres the end result, this is with me coding a library with a function that supports both callbacks and promises; and in a separate file, and without requiring a specific promise library, using that function twice, once with a promise, and once with a callback:
// ./test-lib.js
'use strict'
const Promise = require( 'bluebird' )
module.exports = ( str, cb ) => {
const endResult = ( txt ) => new Promise( ( res, rej ) => res( 'You said ' + txt ) )
return endResult( str ).asCallback( cb );
}
// app.js
'use strict'
const Testlib = require('./test-lib')
Testlib('foo', ( err, data ) => {
if( err )
console.error('ERROR:',err)
else
console.log('DATA:',data)
})
Testlib('bar')
.then( data => {
console.log('DATA:',data)
})
.catch( err => {
console.error('ERROR:',err)
})
// Result:
// DATA: You said foo
// DATA: You said bar
That seems to have worked perfectly! (Thanks to tyscorp the Bluebird Gitter chat)

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

Using Sinon to stub chained Mongoose calls

I get how to stub Mongoose models (thanks to Stubbing a Mongoose model with Sinon), but I don't quite understand how to stub calls like:
myModel.findOne({"id": someId})
.where("someBooleanProperty").equals(true)
...
.exec(someCallback);
I tried the following:
var findOneStub = sinon.stub(mongoose.Model, "findOne");
sinon.stub(findOneStub, "exec").yields(someFakeParameter);
to no avail, any suggestions?
I've solved it by doing the following:
var mockFindOne = {
where: function () {
return this;
},
equals: function () {
return this;
},
exec: function (callback) {
callback(null, "some fake expected return value");
}
};
sinon.stub(mongoose.Model, "findOne").returns(mockFindOne);
Take a look to sinon-mongoose. You can expects chained methods with just a few lines:
sinon.mock(YourModel).expects('findOne')
.chain('where').withArgs('someBooleanProperty')
.chain('exec')
.yields(someError, someResult);
You can find working examples on the repo.
Also, a recommendation: use mock method instead of stub, that will check the method really exists.
Another way is to stub or spy the prototype functions of the created Query (using sinon):
const mongoose = require('mongoose');
sinon.spy(mongoose.Query.prototype, 'where');
sinon.spy(mongoose.Query.prototype, 'equals');
const query_result = [];
sinon.stub(mongoose.Query.prototype, 'exec').yieldsAsync(null, query_result);
If you use Promise, you can try sinon-as-promised:
sinon.stub(Mongoose.Model, 'findOne').returns({
exec: sinon.stub().rejects(new Error('pants'))
//exec: sinon.stub(). resolves(yourExepctedValue)
});
I use promises with Mongoose and stub the methods like this:
const stub = sinon.stub(YourModel, 'findById').returns({
populate: sinon.stub().resolves(document)
})
Then I can call it like:
const document = await YourModel.findById.populate('whatever');
Using Multiple mongoose method stub using below code.
Service code
models.Match.findOne({ where: { id: ChangeInningInputType.id } })
.then((match) => {
if (!match) {
throw Error('Match not found');
}
models.Match.update(
{ inning: ChangeInningInputType.inning },
{ where: { id: ChangeInningInputType.id } },
);
return resolve(match);
})
.catch((error) => {
return reject(error);
});
Testcase code
it('Success test case for inning update', async () => {
const bulkCreateStub = sinon
.stub(models.Match, 'findOne')
.resolves(inningResponse);
const updateStub = sinon
.stub(models.Match, 'update')
.resolves(inningResponse);
const aa = await changeInning(
{},
{
ChangeInningInputType: inningRequest,
},
);
console.log('updateStub===>', updateStub);
expect(updateStub.calledOnce).to.equal(true);
expect(bulkCreateStub.calledOnce).to.equal(true);
});

How to use mongoose Promise - mongo

Can someone give me an example on how to use a Promise with mongoose. Here is what I have, but its not working as expected:
app.use(function (req, res, next) {
res.local('myStuff', myLib.process(req.path, something));
console.log(res.local('myStuff'));
next();
});
and then in myLib, I would have something like this:
exports.process = function ( r, callback ) {
var promise = new mongoose.Promise;
if(callback) promise.addBack(callback);
Content.find( {route : r }, function (err, docs) {
promise.resolve.bind(promise)(err, docs);
});
return promise;
};
At some point I am expecting my data to be present, but how can I access it, or get at it?
In the current version of Mongoose, the exec() method returns a Promise, so you can do the following:
exports.process = function(r) {
return Content.find({route: r}).exec();
}
Then, when you would like to get the data, you should make it async:
app.use(function(req, res, next) {
res.local('myStuff', myLib.process(req.path));
res.local('myStuff')
.then(function(doc) { // <- this is the Promise interface.
console.log(doc);
next();
}, function(err) {
// handle error here.
});
});
For more information about promises, there's a wonderful article that I recently read:
http://spion.github.io/posts/why-i-am-switching-to-promises.html
Mongoose already uses promises, when you call exec() on a query.
var promise = Content.find( {route : r }).exec();
Mongoose 4.0 Release Notes
Mongoose 4.0 brings some exciting new functionality: schema validation
in the browser, query middleware, validation on update, and promises
for async operations.
With mongoose#4.1 you can use any promises that you want
var mongoose = require('mongoose');
mongoose.Promise = require('bluebird');
Another example with polyfilling global.Promise
require('es6-promise').polyfill();
var mongoose = require('mongoose');
So, you can just do later
Content
.find({route : r})
.then(function(docs) {}, function(err) {});
Or
Content
.find({route : r})
.then(function(docs) {})
.catch(function(err) {});
P.S. Mongoose 5.0
Mongoose 5.0 will use native promises by default if available,
otherwise no promises. You will still be able to set a custom promises
library using mongoose.Promise = require('bluebird');, however,
mpromise will not be supported.
I believe you're looking for
exports.process = function ( r, callback ) {
var promise = new mongoose.Promise;
if(callback) promise.addBack(callback);
Content.find( {route : r }, function (err, docs) {
if(err) {
promise.error(err);
return;
}
promise.complete(docs);
});
return promise;
};
On this page:http://mongoosejs.com/docs/promises.html
The title is Plugging in your own Promises Library
Use the bluebird Promise library like this:
var Promise = require('bluebird');
var mongoose = require('mongoose');
var mongoose = Promise.promisifyAll(mongoose);
User.findAsync({}).then(function(users){
console.log(users)
})
This is thenable, such as:
User.findAsync({}).then(function(users){
console.log(users)
}).then(function(){
// more async stuff
})

Resources