Mongoose: write own methods with promise - node.js

I want to write some instance / static methods for a model, which uses the mongoose's API and do something before and after using the mongoose's API.
For example, I write my own Article.createArticle method, it checks the data before Article.create, and return article.toObject() after creation.
this is how I want my createArticle works:
Article.createArticle({someKeys: 'SomeData', ...})
.then(obj => {
// get plain object here
});
I tried to write something like this:
Article.Schema.static({
createArticle: function(data) {
return new Promise(function(resolve, reject){
checkDataKeys(data);
resolve(mongoose.model('Article').create(data)
.then(article => resolve(article.toObject()));
);
});
},
});
with this createArticle, I only get undefined in then,
I must get something wrong.
Also, in addition to make the createArticle work, is there any way to make the code more elegant?
Thanks.

I myself found a methods that works for me. Though I'm not very understand the mechanism, maybe I'll try to work on it later...
ArticleSchema.static({
createArticle: function(data) {
checkDataKeys(data); // pre works
return mongoose.model('Article').create(data)
.then(obj => {
return obj.toObject(); // afterworks
// here, just get the plain obj from doc
// and *return* it
});
},
});
update: after I searched something about Promise, maybe this could be better.
ArticleSchema.static({
createArticle: function(data) {
checkDataKeys(data); // pre works
return mongoose.model('Article').create(data)
.then(obj => {
return Promise.resolve(obj.toObject());
// Some say that this is totally same as directly return
})
.catch(err => {
return Promise.reject(err);
// if error, this will ensure the err will be caught
// for some async cases
});
},
});

Related

What's the proper way to return a promise in this triggered Cloud Function

I understand that for a triggered function, I must always return a promise. Look at the following example:
//Example
exports.onAuthUserDelete = functions.auth.user().onDelete(async (user) => {
let userId = user.uid;
try {
await firestore.collection('Users').doc(userId).delete();
return Promise.resolve();
} catch (error) {
logger.error(error);
return Promise.reject(error);
}
});
My questions are:
Is return Promise.resolve() required or can I just do return firestore.collection('Users').doc(userId).delete()? If I opt to go with the latter, what would happen if the command failed? Will it still trigger catch()?
Is it better to just start every function with the following template to make sure a promise is always returned?
//Is it better to start with this boilerplate
exports.onAuthUserDelete = functions.auth.user().onDelete(async (user) => {
return new Promise((resolve, reject) => {
//My code goes here...
});
}
Firestore's delete operation already returns a promise, so there's no need to create your own. As far as I can see that first example is example the same as:
exports.onAuthUserDelete = functions.auth.user().onDelete((user) => {
return firestore.collection('Users').doc(user.uid).delete();
});
Given that, I highly recommend using the above shorter version.

Unable to use .then in express framework

I'm new to Express framework and learning, I'm having a problem using .then. The problem is I have 2 functions and I want the first one to complete before the second to start executing. I'm exporting the modules.
var ubm = require('./userBasic');
There are 2 functions setUserData and showUserId, the showUserId must execute only after setUserData has performed its operation.
var userId = ubm.setUserData(userName,userEmail,userDOB,moment);
userId.then(ubm.showUserId(userId));
Below are the 2 functions:
module.exports = {
setUserData: function (userName,userEmail,userDOB,moment){
//Performing database activities
return userId;
}
showUserId: function (userId){
console.log(userId);
}
}
When i run it says TypeError: Cannot read property 'then' of undefined.
Like I said I'm very new and learning and was unable to figure out the solution. I did some google search and got a brief about promise, but I don't know how to implement here.
Try using promises
module.exports = {
setUserData: function(userName, userEmail, userDOB, moment) {
return new Promise(function(resolve, reject) {
//db stuff
reject(error);
resolve(userId);
});
},
showUserId: function(userId) {
console.log(userId);
};
};
So in your execution you would write
ubm.setUserData(username, usereEmail, userDOB, moment)
.then((data) => {
showUserId(data);
})
.catch((err) => {
console.log(err);
});
A couple of things to note is that in this instance you could just log data without the need for another function like
ubm.setUserData(username, usereEmail, userDOB, moment)
.then((data) => {
console.log(data);
})
.catch((err) => {
console.log(err);
});
Whatever value you pass into resolve() will be returned as well as you pass errors into reject().

NodeJS fs.appendFile not working within promise in mocha

I want to keep a log during my integration test suite. I'm testing that every 'item' is being compiled and logging how much time it took. I'm using node 4.3.
First of all I create the log file:
before(function() {
if (!fs.existsSync("./log.csv")) {
fs.writeFile("./log.csv", "Name; Time");
}
});
Then within each it block I would do this:
for (const item of items) {
it('compiles', function() {
return item.testCompile();
});
}
And item class has these methods:
testCompile() {
return this
.buildItem()
.then(result => {
// whatever testing stuff
});
}
buildItem() {
return this
.internalLogicForCompiling()
.then(result => {
// This is not appending anything
fs.appendFile("./log.csv", `${item.name}; ${result.compileTime}`);
return result;
});
}
So far the file is created but never updated... Any clue what I'm doing wrong?
PS: I assume if the file doesn't exists, fs should throw an error, however it doesn't.
Your code is generally ignoring the fact that your fs calls are asynchronous. Promises are not magic. If you use code that is asynchronous but does not use promises, you need to do more than plop that code in a promise can call it done.
The easiest way to deal with the issue would be to use fs.writeFileSync and fs.appendFileSync instead of the calls you make. Otherwise, you should write your before like this:
before(function(done) {
if (!fs.existsSync("./log.csv")) {
fs.writeFile("./log.csv", "Name; Time", done);
}
});
I've just added the done callback.
And buildItem could be something like this so that the promise it returns won't resolve before appendFile is done doing its work:
buildItem() {
return this
.internalLogicForCompiling()
.then(result => {
return new Promise((resolve, reject) => {
fs.appendFile("./log.csv", `${item.name}; ${result.compileTime}`, (err) => {
if (err) {
reject(err);
return;
}
resolve(result);
});
});
});
}

Way to use mongodb's find cursor's next/each functions as a promise after promisifying mongodb with Bluebird

The node mongodb docs specify to use next/each for large number of documents so as to not have everything loaded onto memory if we were to use toArray.
So, i thought my sample code should work as is. But it just returns one document.
What should be the correct way to deal with this problem?
This is my code sample :
var findAsync = function (collection,query) {
return mongodb.MongoClient.connectAsync(mongodbServerString)
.then(function (db) {
return [db.collection(collection).find(query), db];
});
};
findAsync("UserProfile",{})
.spread(function (cursor,db) {
return [cursor.project({Email:true}),db];
})
.spread(function (cursor, db) {
return cursor.eachAsync().then(function (doc) {
console.log(doc);
}).catch(function () {
db.close();
});
});
Promises represent singular values. Promises are basically like function returns, since a function cannot return multiple values - it wouldn't make sense to convert each to a promise returning function.
What you can do is either convert it to an Observable returning function and then use .forEach on that to get a promise back for the completion of the sequence or you can implement something similar manually:
function each(cursor, fn) {
return new Promise((resolve, reject) => {
cursor.forEach((err, data) => {
if(err) {
cursor.close();
return reject(err);
}
try { fn(data); } catch(e) { cursor.close(); reject(e); }
}, err => { { // finished callback
if(err) reject(err);
else resolve();
});
});
}
Which would let you write:
each(cursor, doc => console.log(doc)).then(...).catch(...)
Also note that Mongo connections are persistent, you're supposed to connect once when the server starts and then keep the connection open for as long as the server is run.

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.

Resources