Bluebird promise: loop over mongoose result - node.js

I query author data from a MongoDB via Mongoose (MEAN environment). The author data also contains an array of books that the author wrote (-> results.books). Once received, I'd like to iterate through this array of books and check for certain values. This is my code so far.
return Author.findOne({_id: req.user._id}, '-username').execAsync()
.then(function(results) {
return Promise.each(results.books) //this line causes TypeError rejection
}).then(function(book){
console.log('book:'+book); // test output
if(book==='whatever‘){
//do foo
}
}).catch(function(err){
console.log('Error: '+err);
});
Unfortunately I can't get it to work as it keeps giving me a rejection TypeError for the line marked above. I tried to apply this solution here (Bluebird Promisfy.each, with for-loops and if-statements?) but it wouldn't work out as it also seems to be a different kind of problem.

Bluebird's Promise.each() takes an iterable AND a iterator callback function that will be called for each item in the iterable. You are not passing the callback function. The .then() handler after Promise.each() is called when the entire iteration is done. It looks like you're expecting that to be the iterator - that's not the case.
Bluebird doc for Promise.each() is here.
I'm not sure exactly what you're trying to accomplish, but perhaps this is what you want:
return Author.findOne({_id: req.user._id}, 'username').execAsync()
.then(function (results) {
return Promise.each(results.books, function(book) {
console.log('book:' + book); // test output
if (book === 'whatever‘) {
//do foo
}
});
}).then(function() {
// Promise.each() is done here
}).catch(function (err) {
console.log('Error: ' + err);
});

Related

NodeJS Sequelize returning data from query

Totally new to Javascript and Node. I am trying to get started with Sequelize as an ORM and did a simple query
var employeeList = Employee.findAll().then(function(result){
console.log(result.length);
console.log(result[0].employeeName);
//how do I return this to a variable with which I can do further processing
return result;
});
//do something with employeeList
employeeList[0].employeeName //cannot read property of undefined
While the console logs print out the right name the employeeList itself does not contain any data. I tried printing the employeeList and it shows the promise
Promise {
_bitField: 0,
_fulfillmentHandler0: undefined,
_rejectionHandler0: undefined,
_promise0: undefined,
_receiver0: undefined }
I did skim through the promise concept but could not get an east example as to how to return the results from the promise to a variable outside the function. I thought returning the result would do t he trick. Am I missing something here? I can understand that I could work on the results within the promise function. If the scenario is to make two database calls and then process the results of both calls to return a merged result how could that be done without getting results to variables.
so based on your post on comments of using to independent queries I'd like to show you how to use them properly:
//Each request in it's own function to respect the single responsability principle
function getAllEmployees() {
return Employee
.findAll()
.then(function(employees){
//do some parsing/editing
//this then is not required if you don't want to change anything
return employees;
});
}
function doAnotherJob() {
return YourModel
.findAll()
.then(function(response) => {
//do some parsing/editing
//this then is not required if you don't want to change anything
return response;
});
}
function do2IndependentCalls() {
return Promise.all([
getAllEmployees(),
doAnotherJob()
]).then(([employees, secondRequest]) => {
//do the functionality you need
})
}
Another way of doing is use async await :
async function getEmployees(){
var employeeList = await Employee.findAll().then(function(result){
console.log(result.length);
console.log(result[0].employeeName);
return result;
});
employeeList[0].employeeName;
}
The then method returns a Promise, not your employee list.
If you want to use the employee list, you should either do that in the function passed to your existing then call, or in a subsequent then call, as shown below.
Employee
.findAll()
.then(function(el){
console.log(el.length);
console.log(el[0].employeeName);
return el;
})
.then(function(employeeList){
//do something with employeeList
employeeList[0].employeeName
});

flow of program execution of javascript functions in node.js project

I'm having trouble deleting an item from MongoDB array in a node.js program and found an annoying issue in flow of program execution.
Here's the code:
productController.deleteProduct = function(req,res){
productModel.findById(req.query.id,function(err, product){
if(err){
res.send(err);
}
if(storeController.deleteStoreProduct(product,res.locals.store,req)){
product.remove(function(err){
if(err){
res.send(err);
}
res.send('product deleted successfully');
});
}
else{
res.send('delete operation failed');
}
});
}
The above function runs fine. The problem is with the below function.
The above function calls storeController.deleteStoreProduct
Here's the code for storeController.deleteStoreProduct:
storeController.deleteStoreProduct = function(product, store, req){
var isDeleted = false;
storeModel.findById(store, function(err, foundStore){
if(product.category === "electronics"){
if(product.subcategory === "mobiles"){
console.log('beginning');
storeModel.update({"storeId":"store-456"}, {'$pull': {"electronics.mobiles": mongoose.Types.ObjectId(product._id)}});
console.log('yeah done!!');
isDeleted = true;
}
}
});
if(isDeleted === true){
console.log('isdeleted: true');
return true;
}
else{
console.log('isdeleted: false');
return false;
}
}
Here in the storeController.deleteStoreProduct function I have written console.log statements just for the debugging purpose.
When I run this program what it has to do is delete a particular item from storeModel collection but instead it outputs just the console.log statements; the console.log statement above the delete statement and the one below that statement executes fine, but the actual delete statement in the middle of these both console.log statements doesn't executes and neither does it throws an error.
output:
isdeleted: false
beginning
yeah done!!
Instead of running the program from the beginning, it directly goes to last if else statement in storeController.deleteStoreProduct function.
I couldn't understand what is happening here.
more details:
arguments in the function storeController.deleteStoreProduct(product,store,req) are
1.product
this is an object and is something like this:
{
"name":"asus zenfone 2",
"category": "electronics",
"subcategory":"mobiles",
"details":{
"specs":["5mp rear cam","2gb ram",16gb rom],
}
}
2.store
store is the _id of the store object in the mongodb.
3.req
This is the request object
The problem with your code is, you are calling asynchronous function storeModel.findById inside your storeController.deleteStoreProduct function and expect storeController.deleteStoreProduct to behave like a synchronous function.
Your storeController.deleteStoreProduct function is always going to return false.
your code does not print
isDeleted: true
eventhough item gets deleted properly since code block
console.log('isdeleted: true');
return true;
never gets executed.
you cant treat storeController.deleteStoreProduct as a synchronous function. You need to treat storeController.deleteStoreProduct as an asynchronous function by either make it a function which accepts a callback or making it a promise returning function.
Hope this helps.

findOne returns undefined on the server

Here is my code on the server:
Meteor.publish('currentRequest', function (requestId) {
console.log('from publish');
console.log(requestId) // The id is printed successfully
console.log(Requests.findOne({_id: requestId})) // returns undefined
return Requests.findOne({_id: requestId});
});
The item ID is printed but .findOne() doesn't seem to work as it returns undefined.
What am I doing wrong here?
The answer to your question will be: because there is no document satisfying your search query.
According to documentation:
Finds the first document that matches the selector, as ordered by sort and skip options. Returns undefined if no matching document is found.
Equivalent to find(selector, options).fetch()[0] with options.limit = 1.
Also, as it has been pointed by #GaëtanRouziès, this publication won't work, because .findOne returns document/undefined instead of cursor.
.findOne() return the response in asynchronus way. you need to pass a callback function to findOne and use return statement in callback function. Please take a look at the sample code below.
CollectionName.findOne({
query : query
}, function(err, resp) {
if (err) {
throw err;
} else {
return resp;
}
});

Bluebird Warning : a promise was created in a handler but was not returned from it

I have an array of filenames which I iterate using this async module for node.
async.eachSeries(imageStore, function(imageDetails,callback){
mongoMan.updateCollection('imageStore',imageDetails,{_id: imageDetails.fileName}).then(function(res){
return callback(null, res);
}).catch(function(err){
logger.error(err);
return callback(err);
});
},function(err){
callback(null);
});
The updateCollection() function is like this:
exports.updateCollection = function(collection, values, condArr){
var result = imageStores.updateAsync(condArr, values, { upsert: true }).then(function(res){
return new Promise.resolve(res);
}).catch(function(err){
logger.error(err);
});
return new Promise.resolve(result);
}
This code works well, updates DB and everything. But I am still unable to resolve the warning that bluebird throws:
Warning: a promise was created in a handler but was not returned from it
at Object.exports.updateCollection (/home/swateek/Documents/codebase/poc/apps/webapp/server/components/mongodb/mongoConn.js:46:22)
at /home/swateek/Documents/codebase/poc/apps/webapp/server/components/imageStore.js:72:24
at /home/swateek/Documents/codebase/poc/apps/webapp/node_modules/async/lib/async.js:181:20
at iterate (/home/swateek/Documents/codebase/poc/apps/webapp/node_modules/async/lib/async.js:262:13)
Have looked up solutionhere but that ain't convincing enough in my case. Atleast is there a way to turn off the warning?
UPDATE
Please check the correct answer below, and here's how my calling function looks like:
function(callback){// post imageStore data to DB
async.eachSeries(imageStore, function(imageDetails,callback){
mongoMan.updateCollection('imageStore',imageDetails,{_id: imageDetails.fileName}).catch(function(err){
logger.error(err);
return callback(err);
});
return callback(null);
},function(err){
callback(null);
});
}
You are asking for trouble and throwing away programming advantages if you try to mix async library callbacks with promises. Pick one structure or the other and use it everywhere. Personally, I'd suggest you move to promises and just "promisify" functions that currently work with callbacks. If you're using the Bluebird Promise library, then it has Promise.promisify() and Promise.promisifyAll() which make it pretty easy to promisify things that use the standard node.js async callback so you can just control everything with Promise logic.
Now, onto your specific issue. That error means that you are creating promises within a .then() handler, but those promises are not returned or chained onto any previous promise. Thus, they are completely independent from your other chain. This is usually a bug (thus the reason for the warning). The only time one might actually want to do that is you have some fire and forget operation that you don't want the outer promise to wait for and you aren't tracking errors on which is not what you have here.
Change to this:
exports.updateCollection = function(collection, values, condArr){
return imageStores.updateAsync(condArr, values, { upsert: true}).catch(function(err){
logger.error(err);
// rethrow error so the caller will see the rejection
throw err;
});
}
Changes:
Return the main promise rather than creating a new promise.
No need for return new Promise.resolve(res); as res is already returned from that promise so you can remove that whole .then() handler as it isn't doing anything.
No need for return new Promise.resolve(result); as you can just return the earlier promise directly.
FYI, though you don't need it here at all, Promise.resolve() can be called directly without new.
You have some issues here:
var result = imageStores.updateAsync(condArr, values, { upsert: true }).then(function(res){
return new Promise.resolve(res);
}).catch(function(err){
logger.error(err);
});
return new Promise.resolve(result);
Firstly: Promise.resolve is not a constructor so it shouldn't be used with new. Secondly: there is no point calling Promise.resolve( result ) at all, just return result which is already a Promise. The intermediate then is pointless as well. You can reduce that code to:
return imageStores.updateAsync(condArr, values, { upsert: true })
.catch( function(err){
logger.error(err);
} )
;

Bluebird Promisfy.each reference error?

Am new to promisification and am not quite sure if .then and .each carry variables across the entire promise.
Also, I clearly define docReplies in the fourth line, yet the console logs:
Possibly unhandled ReferenceError: docReplies is not defined
Am looking to loop over each element (replyID) in the repliesIDsArray and findOneAsync the message..then for each element in the doc.replies array find the index of the replyID, setting that to index1..then for each element in the doc.replies[index1] array find the index of the username (res.locals.username), setting that to index2..then with index1 and index2, save fields to save to the doc..
(Here's a link to where this derives, with an outline of the db schema if that helps)
Promise.each(repliesIDsArray, function(replyID){
Models.Message.findOneAsync({'_id': req.params.message_id})
.then(function(doc){
var docReplies = [];
pushReplies = docReplies.push(doc.replies);
}).each(docReplies, function (replyIndex){
// loop over doc.replies to find..
// ..the index(index1) of replyID at replies[index]._id
var index1;
if (docReplies[replyIndex]._id == replyID) {
index1 = replyIndex;
}
var docRepliesIndex1 = [];
pushRepliesIndex1 = docRepliesIndex1.push(doc.replies[index1]);
}).each(docRepliesIndex1, function (usernameIndex){
// loop over doc.replies[index1].to and find..
// ..the index(index2) of res.locals.username at replies[index1].to[index2]
var index2;
if (docRepliesIndex1.to[usernameIndex].username === res.locals.username) {
index2 = usernameIndex;
}
}).then(function(index1, index2) {
console.log('index1 = ' + index1);
console.log('index2 = ' + index2);
doc.replies[index1].to[index2].read.marked = true;
doc.replies[index1].to[index2].read.datetime = req.body.datetimeRead;
doc.replies[index1].to[index2].updated= req.body.datetimeRead;
doc.markModified('replies');
var saveFunc = Promise.promisify(doc.save, doc);
return saveFunc();
console.log('doc saved');
}).then(function(saved) {
console.log("Success! doc saved!");
console.log("Sending saved doc:");
res.json(saved);
}).catch(Promise.OperationalError, function(e){
// handle error in Mongoose findOne + save
console.error("unable to save because: ", e.message);
console.log(e);
res.send(e);
throw err;
}).catch(function(err){
// would be a 500 error, an OperationalError is probably a 4XX
console.log(err);
res.send(err);
throw err; // this optionally marks the chain as yet to be handled
});
})
Promises have no magic capability with your variable declarations. docReplies is defined in your first .then() callback function and is only available within that function. If you want it available across many .then() handler functions, then you will need declare it at a higher scope so it's available everywhere (normal Javascript scoping rules).
Or, in certain cases, you can return data from one promise handler to another, but it doesn't sound like that's what you're trying to do here.
In any case, normal Javascript scoping rules apply to all variable declarations, even those in promise callback functions.

Resources