I am attempting to use mongoose with async, everything works fine for the most part...however when I perform a lookup that returns no results my application seems to hang and eventually timeout.
Here is some example controller code that does a simple lookup by id using mongoose and async:
module.exports.find = function(req, res) {
async.waterfall([
function(next) {
SomeModel.findById(req.params.id, next);
},
function(someModel, next) {
if (!SomeModel) {
res.status(404).json({
success: false,
message: 'SomeModel not found'
});
} else {
res.json(SomeModel);
}
}
]);
};
If a record is found everything comes back just fine, however for a nonexistent id it seems that the second async step is never called and eventually the whole request times out.
So what am I doing wrong here? How do I get the 'findById' method to call 'next' even if a record isn't found?
Mongoose is throwing an error, and you aren't catching it. The other thing I should mention is that you should be doing your response handling in the final callback (which you have not defined).
Try something like this:
module.exports.find = function(req, res) {
async.waterfall([
function(next) {
SomeModel.findById(req.params.id, next);
}
], function(err, SomeModel){
// this is the final callback
if (err) {
// put error handling here
console.log(err)
}
if (!SomeModel) {
res.status(404).json({
success: false,
message: 'SomeModel not found'
});
} else {
res.json(SomeModel);
}
});
};
Alternatively, you could just simplify it to not use waterfall:
module.exports.find = function(req, res) {
SomeModel.findById(req.params.id, function(err, SomeModel){
if (err) {
// put error handling here
console.log(err)
}
if (!SomeModel) {
res.status(404).json({
success: false,
message: 'SomeModel not found'
});
} else {
res.json(SomeModel);
}
});
};
Related
So I'm working on a movie website to practice my node.js/angular skills. Recently I noticed that I have a lot of nested asynchronous functions on my server side. Many people suggested using the async.js module for this.
Below there are two different versions of the same server side objective. One is the original nested async functions and the other one is what I came up with using Async.js. Could someone see if I did it the right way, used the right method in this case (waterfall) and perhaps give me some corrections that could improve my code? It works, but maybe you guys can see some improvements? For example: Is there an alternative to passing the variables to the arguments every time ( like I did with reviewId e.g)
Extra question:
What are the most used methods of the async.js module? Cause there are like hundreds and I would just like to learn the most important ones.
This is what I came up with when I tried to translate the nested asynchronous functions below
router.delete('/deleteReview/:reviewId', function(req, res, next) {
async.waterfall([
function (callback) {
var reviewId = req.params.reviewId;
Review.find({'_id': reviewId})
.populate('user')
.populate('movie')
.exec(function(err, review){
callback(null, review, reviewId)
});
},
function (review, reviewId, callback) {
var index = review[0].user.reviews.indexOf(reviewId);
review[0].user.reviews.splice(index, 1);
review[0].user.save(function(err, user){
callback(null, user, reviewId, review)
})
},
function (user, reviewId, review, callback) {
var index = review[0].movie.reviews.indexOf(reviewId);
review[0].movie.reviews.splice(index, 1);
review[0].movie.save(function(err, movie){
callback(null, movie, index)
});
}
], function (err, result, index) {
if (err) {
res.status(500).json({
message: "An error occurred",
obj: err
})
}
res.status(200).json({
message: "successful",
obj: index
})
});
});
the original nested asynchronous functions
router.delete('/deleteReview/:reviewId', function(req, res, next){
var reviewId = req.params.reviewId;
Review.find({'_id': reviewId})
.populate('user')
.populate('movie')
.exec(function(err, review){
if (err) {
res.status(500).json({
message: "An error occurred",
obj: err
})
}
var index = review[0].user.reviews.indexOf(reviewId);
review[0].user.reviews.splice(index, 1);
review[0].user.save(function(err, user){
if (err) {
res.status(500).json({
message: "An error occurred",
obj: err
})
}
var index = review[0].movie.reviews.indexOf(reviewId);
review[0].movie.reviews.splice(index, 1);
review[0].movie.save(function(err, movie){
if (err) {
res.status(500).json({
message: "An error occurred",
obj: err
})
}
res.status(200).json({
message: "successful",
obj: index
})
})
});
})
});
I'm writing a rest api for a node application, and I find myself rewriting something like the following a lot:
function(req, res, next) {
databaseCall()
.then( (results) => {
if (results != null) {
res.status(200).send(results);
} else {
res.sendStatus(404);
}
})
.catch(function(err) {
console.log("Request error: " + err.stack);
res.sendStatus(500);
})
}
I would like to refactor the response portion, so I can do something like
databaseCall()
.then(handleResponse)
where handleResponse would take care of the whole response/catch process.
But I can't quite figure out how to do that. The databaseCall method varies depending on the endpoint - sometimes it takes a parameter, sometimes not. I could make a generic function expression that takes the databaseCall result and stick it in the promise chain, but I don't know how I could access the response object inside that function. I know I could add another function to combine everything, like so:
function(databaseCall, parameter, req, res, next) {
databaseCall(parameter)
.then( (results) => {
if (results != null) {
res.status(200).send(results);
} else {
res.sendStatus(404);
}
})
.catch( (err) => {
console.log("Request error: " + err.stack);
res.sendStatus(500);
})
}
But that seems ugly since databaseCall could have 0-several parameters. I'd think there's a more elegant solution.
You're probably thinking in the right direction, you just need to take it a step further and keep the db call outside the generic handler, and pass it as a promise instead
// generic handler for db promise
// the promise is created outside and passed as arg
function responseFromDb(databaseCallPromise, res) {
databaseCallPromise
.then((results) => {
if (results != null) {
res.status(200).send(results);
} else {
res.sendStatus(404);
}
})
.catch((err) => {
console.log(`Request error: ${err.stack}`);
res.sendStatus(500);
});
}
// handler per request, only needs to create db call with the desired params
// and pass it to the generic handler, which will take care of sending the response
function(req, res, next) {
responseFromDb(databaseCall(param1, param2), res)
}
I've got my array of queries
var array = [ 'UPDATE EVALUATION SET mark = "16" WHERE id_eval = "21" AND id_usr = "125"',
'UPDATE EVALUATION SET mark = "9" WHERE id_eval = "22" AND id_usr = "125"',
'UPDATE EVALUATION SET mark = "8" WHERE id_eval = "34" AND id_usr = "125"'
]
However, when I try to execute them all at once with async, my web page says Waiting for localhost... and it keeps on loading forever. What am I doing wrong?
async.forEach(array, function(query, callback) {
connection.query(query, function(err, rows, fields) {
if(err) {
return console.error(err);
}
callback();
});
}, function(err){
if(err) {
return console.log(err);
}
});
Just make sure that you return the response after the forEach callback is called:
async.forEach(array, function(query, callback) {
connection.query(query, function(err, rows, fields) {
if(err) {
console.error(err);
}
callback();
});
}, function(err){
if(err) {
console.log(err);
}
res.redirect('/next-page');
});
That way, the redirection will occur only at the end of all the queries.
Some things you should verify:
Verify you didn't call res.end() or res.redirect() or anything similar before the above code.
Verify that your DB query method actually expect only 2 arguments: query and callback, and not anything in between (e.g. the query parameters).
Verify this piece of code is actually called when you expect it. Try debugging the request all the way.
Currently there's no real error handling here. You should consider returning an HTTP error if something went wrong. This should also help you debugging this code in the future.
Usually you have a request handler like this:
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Hello World\n');
});
Your browser holds the connection until res.endwas called!
In your environment it should look somethinge like
const server = http.createServer((req, res) => {
// ...
async.forEach(array, function(query, callback){
connection.query(query, function(err, rows, fields) {
// you may do some work here but leave it *alyways* via callback!
callback(err);
});
}, function(err){
if(err){ // this may be errors from above
return console.log(err);
}
// Exit here !
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('All done.\n');
});
});
...or a bit cleaner
function doSomeDatabaseUpdates(onFinished) {
var array = [...];
async.forEach(array, function(query, callback){
connection.query(query, function(err, rows, fields) {
// you may do some work here but leave it *alyways* via callback!
callback(err);
});
},
function IGetCalledAfterAboveCallbackWereExecuted(err){
if(err){ // this may be errors from above
return console.log(err);
}
// Exit here !
onFinished();
});
}
const server = http.createServer((req, res) => {
// ... do some work
if (doSomeUpdated === true) {
doSomeDatabaseUpdates(function calledAfterUpdates() {
res.end("updates something");
});
} else {
res.end("nothing to do");
}
});
I have an API in Node.js. My routes look like
exports.getUserList = (req, res, next) => {
User.find().sort({ name: 1 }).then(users => {
res.json({
status: 'success',
users
});
}).catch(next);
};
As seen in the example, I use .catch(next). But is this the correct way to do it? Shouldn't the route always print json?
So I am thinking of doing something like
exports.getUserList = (req, res, next) => {
User.find().sort({ name: 1 }).then(users => {
res.json({
status: 'success',
users
});
}).catch(err => {
res.json({
status: 'error',
msg: err
});
});
};
but shouldn't it then be something like res.status(some_status_code).json({})?
How is a simple API normally carried out in terms of error handling?
What if I, in the code, use a variable that is not defined (i.e. causing a syntax error)? Should I handle it with a JSON error or should I just make sure that I don't do sloppy coding? :-D
Also, is this the fastest way to print the json? I mean, should I use User.find().lean()? Should I do some caching? Is it even clever to store my API on a normal website or are there optimized API servers for such cases?
Have you try async/await function and custom response for success and error?
Here the example :
responseHandler.js
// use this for success
function successResponse(message, data) {
const success = {
success: true,
message: message,
data: data
}
return success
}
// use this as middleware error handler
function errorResponse(err,req,res,next) => {
return res.status(err.status || 500).json({
success: false,
message: err.message
})
}
module.exports = {
successResponse,
errorResponse
}
myRouter.js
const { successResponse } = require('./responseHandler')
exports.getUserList = async (req, res, next) => {
await User.find().sort({ name: 1 }).then(users => {
res.status(200).json(
successResponse(`data user`, users)
)
}).catch(err => {
return err
});
};
I've been looking at other Stack Overflow questions and I still can't figure mine out. I'm getting this error in the browser console:
Exception in delivering result of invoking 'formMethod1': TypeError: callback is not a function
I've put my code below and a comment on the line the error references. It seems that the "err" object isn't getting passed, but the callback is actually being called and the whole thing goes through, it just never catches the error.
submitForm1(entry,
processForm1(err,res,entry,function(err,res){
//Done processing
console.log(err); //Doesn't work
console.log(res); //Doesn't work
console.log("Done"); //Works
})
)
function submitForm1(entry, callback) {
Meteor.call('formMethod1', {
params: {
user: Meteor.user().username,
activity: entry
}
}, function(err,res){
if(err){
console.log(err) //Works
callback(err, res, entry) //This is where the error happens
} else{
callback(undefined, res, entry)
}
}
);
}
function processForm1(err, res, entry, callback) {
console.log(err); //Doesn't work
console.log(res); //Works
console.log(entry); //Works
if (err) {
if (err.error == "1001") { //Activity not found
//Handle Error
callback("Activity Not Found");
} else {
//Handle Error
callback(err.message);
}
} else { //No Errors
callback(undefined,"Submitted");
}
}
EDIT: You all got me going in the right direction. Here's the corrected code:
submitForm1(entry, function(err,res){
processForm1(err,res,entry,function(err,res){
//Done processing
console.log(err);
console.log(res);
console.log("Done");
})
});
You are calling the function you pass as callback, instead of ... passing it, but then pass another function as argument to that. I think you mixed up two flavours of the same function together in a wrong mix. Fix like this:
submitForm1(entry,
function (err,res,entry) {
//Done processing
console.log(err);
console.log(res);
console.log("Done");
}
)
You are passing processForm1 as a callback to submitForm1. If you do this you have to make sure that the signature which is called inside processForm1 matches (err, res, entry, callback).
This means that you need to pass a method as callback inside submitForm1 which gets called inside it. Makes sense?
For simplicity let's put it this way:
function submitForm1(entry, function(err,res,entry,function(err,res){
//Done processing
console.log(err); //Doesn't work
console.log(res); //Doesn't work
console.log("Done"); //Works
}) {
Meteor.call('formMethod1', {
params: {
user: Meteor.user().username,
activity: entry
}
}, function(err,res){
if(err){
console.log(err) //Works
callback(err, res, entry) //This is where the error happens
} else{
callback(undefined, res, entry)
}
}
);
}
No you see what's wrong.