I try to understand how I can stop or exit on error in a Nodejs route.
In the code below, I check if header UID is sent and if the fiels group is also sent.
The problem is that nodejs continues to execute the rest of the code even if I use res.end () + return; I wish that Node Js stop everything when I display an error. Perhaps because I do not know much in Node Js I myself take it badly and I have to work otherwise. Can you explain to me and give me an example of how I should do?
var uid;
var group;
if (typeof req.headers['uid'] == 'undefined' || req.headers['uid'] == '') {
res.status(404);
res.json('user_id not set');
res.end();
return;
}
else
{
uid = req.headers['uid'];
User.find({_id:uid}).exec(function(err, data)
{
if(err){
res.status(404);
res.json('user not found');
res.end();
return;
}
});
}
if (typeof req.body.group == 'undefined' || req.body.group == '') {
res.status(500);
res.json('group not defined');
res.end();
return;
}
else
{
group = req.body.group;
}
it is unclear exactly what you mean by "node js stop everything". If you want to stop the entire nodejs process, you can use process.exit().
If you're trying to keep some code after your error from executing and your error occurs in an async callback, then you can't do that. The other code has already executed.
If you want to serialize asynchronous operations so that you complete one async operation BEFORE you decide whether to start the next operation, then you will need to code that differently. You will need to execute the second block of code from within the completion callback of the first async operation.
One aspect of your code that you may not understand is that this block is asynchronous:
User.find({_id:uid}).exec(function(err, data)
{
if(err){
res.status(404);
res.json('user not found');
res.end();
return;
}
});
The callback you pass to .exec() is called sometime LATER. Meanwhile, the rest of your JS has already executed. In addition, when you do a return from within that callback that doesn't return from your outer function, it only returns from that callback function back into the bowels of .exec(). It stops any more of the callback from executing, but has no effect at all on the outer function because that outer function.
So, if you want the .find() operation to finish before you execute the rest of your code in that function, you have to put that code inside the callback function.
Related
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.
I'm working on a homemade RBAC system with Nodejs Express, based on two levels:
First, verify if the user has the right role to perform this action.
Second, verify if the user has the right plan to perform this action.
, I create a middleware like this:
exports.can = function (resource, action) {
return function (request, response, next) {
action = action || request.method;
if (!request.user) {
return next(new errors.UnauthorizedError());
}
request.user.can(request.user.role, request.user.currentPack, resource, action, function (can, error) {
if (error) return next(error);
if (!can) {
return next(new errors.UnauthorizedError());
}
return can;
});
return next();
};
};
I added to my User model this method:
const rbac = new RBAC(rbacJson);
const pack = new PACK(packJson);
schema.method('can', function (role, userPack, resource, action, next) {
let can = false;
action = action.toUpperCase();
can = rbac.can(role, resource, action);
if (can) {
can = pack.can(userPack, resource, action, function (can) {
return next(can);
});
}
return next(can);
});
In my method pack.can(...) I need to execute a mongoose query like this:
PACK.prototype.can = function (pack, resource, action, next) {
let can = true;
// some sequantial code
Trader.count({/* some conditions */}, function (err, count) {
if(count == 0) return next(true);
return next(false);
});
return can;
};
My problem is when the return of Mongoose query is next(false), I have this error:
Error: Can't set headers after they are sent.
at ServerResponse.OutgoingMessage.setHeader (_http_outgoing.js:356:11)
at ServerResponse.header (/home/invoice/node_modules/express/lib/response.js:730:10)
at ServerResponse.send (/home/invoice/node_modules/express/lib/response.js:170:12)
at ServerResponse.json (/home/invoice/node_modules/express/lib/response.js:256:15)
at ServerResponse.response.apiResponse (/home/invoice/server/config/middlewares/api.js:10:14)
at /home/invoice/server/controllers/api/invoice/traders.js:130:21
at /home/invoice/node_modules/mongoose/lib/model.js:3835:16
at /home/invoice/node_modules/mongoose/lib/services/model/applyHooks.js:162:20
at _combinedTickCallback (internal/process/next_tick.js:73:7)
at process._tickDomainCallback (internal/process/next_tick.js:128:9)
After an investigation, I found that the error is maybe due to the double callback Call:
can = pack.can(userPack, resource, action, function (can) {
return next(can);
});
return next(new errors.UnauthorizedError());
But I don't know how to solve this.
I hope that I well explained my problem.
So let's start with the error:
Can't set headers after they are sent.
Nine times out of ten this is caused by trying to send two responses to the same request. The reference to headers is a little misleading, though technically true. The first thing the second response will try to do is set some headers, which will fail because the first response has already sent them back to the client.
The stack trace gives you a clear indication of where the second response originates, including filenames and line numbers. More difficult is to track down the first response, generally I'd just throw in some extra console logging to figure it out.
In the question you mention that you believe you've found the source of the problem but it looks to me like it might just be the tip of the iceberg. You've repeatedly used the same pattern and even if you fix it in one place that may not be sufficient.
Before I get into that, let's start with this:
return next();
For the purposes of this example it doesn't really matter whether you pass an error, e.g. return next(err);, the point is the same. First it invokes next(), which returns undefined. It then returns undefined from the surrounding function. In other words, it's just a convenient shorthand for this:
next();
return;
The reason why we return is to ensure that nothing else happens after we've called next(), we always try to ensure that calling next() is the last thing we do in our handler, not least because otherwise we can have bugs where we try to send the response twice.
The (anti-)pattern you've used looks a bit like this:
obj.doSomething(function() {
return next(); // next 1
});
return next(); // next 2
Again, it doesn't really matter whether you're calling next() or next(err), it all works out much the same. The key thing to note is that the return for next 1 is just returning from the function passed to doSomething. It isn't doing anything to prevent next 2 from being hit. Both next 1 and next 2 will be called.
In places your code seems unclear about whether it's trying to be synchronous or asynchronous, using callbacks and return values at the same time. It makes it a little bit difficult to say for sure what the 'correct' code should look like. Specifically, is the value of can supposed to be returned synchronously or passed to the callback asynchronously? I suspect it's the latter but the current code seems torn between the two. It's important to ensure that you don't call next() until you're ready for the next thing to happen, so if you're waiting on a DB query you mustn't call next until that comes back.
Personally I'd rename your callbacks so they aren't all called next, I find that really confusing. When I see next I'm expecting it to be an Express next function, not just an arbitrary callback.
It's a bit of a guess but I'd suggest your middleware should look something like this:
exports.can = function (resource, action) {
return function (request, response, next) {
action = action || request.method;
if (!request.user) {
return next(new errors.UnauthorizedError());
}
request.user.can(request.user.role, request.user.currentPack, resource, action, function (can, error) {
if (error) {
next(error);
}
else if (can) {
next();
}
else {
next(new errors.UnauthorizedError());
}
});
// Do not call next() here
};
};
The relevant section of the User model would then be:
if (can) {
pack.can(userPack, resource, action, function (can) {
next(can);
});
}
else {
next(can);
}
This is some of my code that I have in my index.js. Its waiting for the person to visit url.com/proxy and then it loads up my proxy page, which is really just a form which sends back an email and a code. From my MongoDB database, I grab the users order using the code, which contains some information I need (like product and the message they're trying to get). For some reason, it seems like its responding before it gets this information and then holds onto it for the next time the form is submitted.
The newline in my res.send(product + '\n' + message) isnt working either, but thats not a big deal right now.
But.. for example, the first time I fill out the form ill get a blank response. The second time, I'll get the response to whatever I filled in for the first form, and then the third time ill get the second response. I'm fairly new to Web Development, and feel like I'm doing something obviously wrong but can't seem to figure it out. Any help would be appreciated, thank you.
app.get('/proxy', function(req,res){
res.sendFile(__dirname+ "/views/proxy.html");
});
var message = "";
var product = "";
app.post('/getMessage', function(req,res)
{
returnMsg(req.body.user.code, req.body.user.email);
//res.setHeader('Content-Type', 'text/plain');
res.send(product + "\n" + message);
});
function returnMsg(code, email){
MongoClient.connect(url, function(err, db){
var cursor = db.collection('Orders').find( { "order_id" : Number(code) })
cursor.each(function(err, doc){
assert.equal(err, null);
if (doc!= null)
{
message = doc["message"];
product = doc["product"];
}
else {
console.log("wtf");
// error code here
}
});
console.log(email + " + " + message);
var document = {
"Email" : email,
"Message" : message
}
db.collection("Users").insertOne(document);
db.close();
});
}
You need to do lots of reading about your asynchronous programming works in node.js. There are significant design problems with this code:
You are using module level variables instead of request-level variables.
You are not correctly handling asynchronous responses.
All of this makes a server that simply does not work correctly. You've found one of the problems already. Your async response finishes AFTER you send your response so you end up sending the previously saved response not the current one. In addition, if multiple users are using your server, their responses will tromp on each other.
The core design principle here is first that you need to learn how to program with asynchronous operations. Any function that uses an asynchronous respons and wants to return that value back to the caller needs to accept a callback and deliver the async value via the callback or return a promise and return the value via a resolved promise. The caller then needs to use that callback or promise to fetch the async value when it is available and only send the response then.
In addition, all data associated with a request needs to stay "inside" the request handle or the request object - not in any module level or global variables. That keeps the request from one user from interfering with the requests from another user.
To understand how to return a value from a function with an asynchronous operation in it, see How do I return the response from an asynchronous call?.
What ends up happening in your code is this sequence of events:
Incoming request for /getMessage
You call returnMsg()
returnMsg initiates a connection to the database and then returns
Your request handler calls res.send() with whatever was previously in the message and product variables.
Then, sometime later, the database connect finishes and you call db.collection().find() and then iterate the cursor.
6/ Some time later, the cursor iteration has the first result which you put into your message and product variables (where those values sit until the next request comes in).
In working out how your code should actually work, there are some things about your logic that are unclear. You are assigning message and product inside of cursor.each(). Since cursor.each() is a loop that can run many iterations, which value of message and product do you actually want to use in the res.send()?
Assuming you want the last message and product value from your cursor.each() loop, you could do this:
app.post('/getMessage', function(req, res) {
returnMsg(req.body.user.code, req.body.user.email, function(err, message, product) {
if (err) {
// send some meaningful error response
res.status(500).end();
} else {
res.send(product + "\n" + message);
}
});
});
function returnMsg(code, email, callback) {
let callbackCalled = false;
MongoClient.connect(url, function(err, db) {
if (err) {
return callback(err);
}
var cursor = db.collection('Orders').find({
"order_id": Number(code)
});
var message = "";
var product = "";
cursor.each(function(err, doc) {
if (err) {
if (!callbackCalled) {
callback(err);
callbackCalled = true;
}
} else {
if (doc != null) {
message = doc["message"];
product = doc["product"];
} else {
console.log("wtf");
// error code here
}
}
});
if (message) {
console.log(email + " + " + message);
var document = {
"Email": email,
"Message": message
}
db.collection("Users").insertOne(document);
}
db.close();
if (!callbackCalled) {
callback(null, message, product);
}
});
}
Personally, I would use promises and use the promise interface in your database rather than callbacks.
This code is still just conceptual because it has other issues you need to deal with such as:
Proper error handling is still largely unfinished.
You aren't actually waiting for things like the insert.One() to finish before proceeding.
router.post("/application_action", function(req,res){
var Employee = req.body.Employee;
var conn = new jsforce.Connection({
oauth2 : salesforce_credential.oauth2
});
var username = salesforce_credential.username;
var password = salesforce_credential.password;
conn.login(username, password, function(err, userInfo, next) {
if (err) { return console.error(err); res.json(false);}
// I want this conn.query to execute first and then conn.sobject
conn.query("SELECT id FROM SFDC_Employee__c WHERE Auth0_Id__c = '" + req.user.id + "'" , function(err, result) {
if (err) { return console.error(err); }
Employee["Id"] = result.records[0].Id;
});
//I want this to execute after the execution of above query i.e. conn.query
conn.sobject("SFDC_Emp__c").update(Employee, function(err, ret) {
if (err || !ret.success) { return console.error(err, ret);}
console.log('Updated Successfully : ' + ret.id);
});
});
I have provided my code above. I need to modify Employee in the conn.query and use it in conn.sobject. I need to make sure that my first query executes before 2nd because I am getting value from 1st and using in the 2nd. Please do let me know if you know how to accomplish this.
New Answer Based on Edit to Question
To execute one query based on the results of the other, you put the second query inside the completion callback of the first like this:
router.post("/application_action", function (req, res) {
var Employee = req.body.Employee;
var conn = new jsforce.Connection({
oauth2: salesforce_credential.oauth2
});
var username = salesforce_credential.username;
var password = salesforce_credential.password;
conn.login(username, password, function (err, userInfo, next) {
if (err) {
return console.error(err);
res.json(false);
}
// I want this conn.query to execute first and then conn.sobject
conn.query("SELECT id FROM SFDC_Employee__c WHERE Auth0_Id__c = '" + req.user.id + "'", function (err, result) {
if (err) {
return console.error(err);
}
Employee["Id"] = result.records[0].Id;
//I want this to execute after the execution of above query i.e. conn.query
conn.sobject("SFDC_Emp__c").update(Employee, function (err, ret) {
if (err || !ret.success) {
return console.error(err, ret);
}
console.log('Updated Successfully : ' + ret.id);
});
});
});
});
The only place that the first query results are valid is inside that callback because otherwise, you have no way of knowing when those asynchronous results are actually available and valid.
Please note that your error handling is unfinished since you don't finish the response in any of the error conditions and even in the success case, you have not yet actually sent a response to finish the request.
Original Answer
First off, your code shows a route handler, not middleware. So, if you really intend to ask about middleware, you will have to show your actual middleware. Middleware that does not end the request needs to declare next as an argument and then call it when it is done with it's processing. That's how processing continues after the middleware.
Secondly, your console.log() statements are all going to show undefined because they execute BEFORE the conn.query() callback that contains the code that sets those variables.
conn.query() is an asynchronous operation. It calls its callback sometime IN THE FUTURE. Meanwhile, your console.log() statements execute immediately.
You can see the results of the console.log() by putting the statements inside the conn.query() callback, but that is probably only part of your problem. If you explain what you're really trying to accomplish, then we could probably help with a complete solution. Right now, you're just asking questions about flawed code, but not explaining the higher level problem you're trying to solve so you're making it hard for us to give you the best answer to your actual problem.
FYI:
app.locals - properties scoped to your app, available to all request handlers.
res.locals - properties scoped to a specific request, available only to middleware or request handlers involved in processing this specific request/response.
req.locals - I can't find any documentation on this in Express or HTTP module. There is discussion of this as basically serving the same purpose as res.locals, though it is not documented.
Other relevants answers:
req.locals vs. res.locals vs. res.data vs. req.data vs. app.locals in Express middleware
Express.js: app.locals vs req.locals vs req.session
You miss the basics of the asynchronous flow in javascript. All the callbacks are set to the end of event loop, so the callback of the conn.query will be executed after console.logs from the outside. Here is a good article where the the basic concepts of asynchronous programming in JavaScript are explained.
I'm trying to learn how to use NodeUnit to test my NodeJs code. Ive written the following code, however each time I run the test, the result is OK: 0 assertions, no matter whether the input parameter is valid or not. Can anyone explain why this doesn't work and how I can fix it?
auth.class.js: This function accepts a user ID and returns a username.
exports.username = function(uid, callback) {
db.query('SELECT username FROM ul_logins WHERE id=?', uid, function(err, results){
if (err) throw new Error(err);
if(results.length > 0)
{
callback(null, results[0].username);
}
else
throw new Error("No results.");
});
};
authtest.js: This test will run successful every time, no matter what the uid is, and count 0 assertions.
var auth = require('./auth.class.js');
exports['username'] = function (test) {
auth.username(1, function(err, data){
return test.equal(data, "joe#example.com");
});
test.done();
};
The function you are testing is asynchronous (see the rewritten function below). You need to put the test.done() in the callback function:
exports['username'] = function (test) {
test.expect(2);
auth.username(1, function (err, data) {
test.ifError(err);
test.equal(data, 'joe#example.com');
test.done();
});
};
In your version, test.done gets called before the callback to auth.username is called. Use test.expect to tell nodeunit how many asserts it should receive before test.done is called.
You have a serious problem in your auth.username function. You should not throw in asynchronous code since a try-catch cannot be used to catch the error, leading to uncaught exceptions. You should always pass errors in asynchronous code to a callback as the first argument (convention). Your function should look like:
exports.username = function (uid, callback) {
var query = 'SELECT username FROM ul_logins WHERE id=?';
db.query(query, uid, function (err, results) {
if (err) {
return callback(err);
}
if (results.length === 0) {
return callback(new Error('No user found.'));
}
callback(null, results[0].username);
});
};
Note that errors are handled first (keeps the code neat), and callback calls in the branches are returned to avoid calling the callback multiple times (using else adds a level of indentation).
I get the impression that you are new to Node, and I remember having difficulty over this point too. Consider any function that does something over the network as asynchronous. This could take a long time, so to avoid blocking the process Node gets the OS to handle the task, and takes a callback to call at some point in the future when the OS gives it a result. This is why Node relies so heavily on callbacks. It leaves the Node process free to do other things (like call test.done in your question) while it waits. It also means that try-catch (which can only catch errors thrown in the same 'tick') no longer works. The conventional way to handle errors is to call the callback with the error as the first argument. All other arguments are for actual results.