and thanks to be there.
Issue :
I'm making a tiny mongoose "middleware" to handle a mongoose error :
// callback function called at each mongoDB response
var handleDbRes = function(callback) {
return function (err, entries) {
if (err) {
err.status = 500;
return next(err);
}
return callback(entries) // that line throw the exception
}
};
And so I'm using it into an api endpoint, e.g. :
someRouter.get('/', function(req, res) {
models.article.find(handleDbRes(res.json))
})
With that code, I encounter an error :
TypeError: Cannot call method 'get' of undefined
I followed the exception and looked at res.json() declaration, when debugging, I figured out :
var app = this.app;
var *** = app.get('***') // that line throw the exception
I guess that app is not defined cause app doesn't exists in "this".
Please can you help me to solve this problem ? I think that the reason is simple but I don't get it...
Thanks you for listening ;)
EDIT : I tried to res.json.bind(res) and it worked, as I thought, but that's really awful to bind this way for most api endpoint and I guess there is another way to do that kind of functionality without that.
EDIT : Thanks to Mscdex advices, I modified my code this way :
.get('/', function(req, res, next) {
models.article.find(handleDbRes(res.json.bind(res), next))
...
...
// callback function called at each mongoDB response
var handleDbRes = function(successCallback, errorCallback) {
return function (err, entries) {
if (err) {
err.status = 500;
return errorCallback(err);
}
return successCallback(entries)
}
};
When you pass res.json, the context for the json() function is lost (it no longer knows what this is because it is not bound). So here are a few possible solutions:
Use a bound version of the function so that this inside json() will always evaluate correctly:
someRouter.get('/', function(req, res) {
models.article.find(handleDbRes(res.json.bind(res)))
})
Or use a wrapper function instead:
someRouter.get('/', function(req, res) {
function respondJSON(val) {
res.json(val);
}
models.article.find(handleDbRes(respondJSON))
})
Or just pass in res and call res.json() inside handleDbRes():
someRouter.get('/', function(req, res) {
models.article.find(handleDbRes(res))
})
// callback function called at each mongoDB response
var handleDbRes = function(res) {
return function(err, entries) {
if (err) {
err.status = 500;
return next(err);
}
res.json(entries);
}
};
The other problem is that handleDbRes() doesn't have access to next, so you need to also pass that function in for when you run into an error.
Related
I am working with MongoDB using Mongoose. Most of the opeartion works with callback. An error may occur while saving/updating/finding a document. Though we can always check if there an error in callback function (as shown in below code) but I want to know while developing how can we generate error and test these blocks?
Tank.findById(id, function (err, tank) {
if (err) return handleError(err);
tank.size = 'large';
tank.save(function (err) {
if (err) return handleError(err);
res.send(tank);
});
});
Are you familiar with the Error class? Emiting errors with the EventEmitter? Throwing errors with throw?
This link is a fairly extensive overview on how to deal with errors in node.
Assuming your using express, in the case of the example you provided, I would usually create an instance of the Error class doing something like:
exports.findTankById = function(req, res, next) {
var id = req.params.id;
Tank.findById(id, function (err, tank) {
if (err) {
var e = new Error("Failed to find tank");
e.data = err;
// attach other useful data to error class instance
return next(e);
}
return res.status(200).json({ data: tank });
})
});
Then in another part of the application, have a middleware function that catches errors passed by your routes via next(). That function could log the error or doing something more creative. Note that when using new Error(...) you can access the stack using the stack attribute of the Error class (e.g. err.stack). After processing the error, the error handler function would send back an appropriate response.
A simple error handler function could look something like:
app.use(function (err, req, res, next) {
if(err.data) {
// caught operational errors
/* do something to log or process error */
var response = {
type : 'error',
description : err.message // this would use "Failed to get tank" for above example
};
res.status(500).json(response);
} else {
// unexpected errors
var domainThrown = err.domain_thrown || err.domainThrown;
var msg = 'domainThrown: ' + domainThrown + '\n' + err.stack;
console.error('%s %s\n%s', req.method, req.url, msg);
res.set('Connection', 'close');
res.setHeader('content-type', 'text/plain');
res.status(503).send(msg + '\n');
}
});
If you like this approach, I usually define more specific error objects of my own that more or less extend the Error class. Using functions to create the more specific error types limits the need to write out the
var e = new Error("Failed to find tank");
e.data = err;
/* attach other useful data to error class instance */
part every time. Using more specific error objects also forces consistency on how the errors are being formatted. Hope that is helpful,
Craig
Ok, lets say I have two Models. Contract and CommLog. Both work find independently but I need many CommLog to relate to each Contract.
In the ContractSchema trying async
ContractSchema.methods.getCommLog = function getCommLog(){
var log = false;
async.parallel([
function(){
CommLog.find({commType:'contract',parent:this._id},function(err,comms){
log = comms;
});
}],
function(){return log;});
};
Where I am trying to use it
router.get('/:code', function(req, res, next) {
Contract.findOne({accessCode:req.params.code},function(err,contract){
if(err)
res.send(err);
var data;
if(contract != null){
var comms = contract.getCommLog();
data = {error:false,data:contract,commlog:comms}
}else{
data = {error:true,message:"No Contract"}
}
res.json(data);
});
});
Where it shows var comms = contract.getCommLog(); It is never returning anything because the getCommLog() is not executing async...
I think its my misunderstanding of mongoose querying, so if you understand what I am trying to accomplish, please let me know what I am doing wrong. I have tried without async which would always return false.
The find call can return all matching results with one query, so I don't think you need async here. The reason it is not populating correctly when you call res.json(data) is because you are not waiting for the method call to finish before you fire off your server response. You would be better off nesting an additional CommLogs.find call within the Contract.find call, and only sending your response once that finishes.
//pseudo code:
Contract.find({}, function(err, contract) {
if(err || !contract) {
//return error response
}
else {
CommLogs.find({contract: contract._id}, function(err, commlogs) {
if(err || !commlogs) {
//return error response 2
}
else {
res.json({errors: false, contract: contract, commlogs: commlogs});
}
});
}
}
I'm using a generic rest api that allow passing the mongo collection name on the request and serve it's content.
My LIST command looks like that:
router.get('/:collectionName', function(req, res, next) {
req.collection.find().sort('-created_on').exec(function(e, results){
if (e) return next(e);
res.send(results)
});
});
It works great.
My problem is that I want every list query to populate sub objects if exists.
I tried:
req.collection.find().populate().sort..
But obviously I get an error:
TypeError: utils.populate: invalid path. Expected string. Got typeof undefined
Help?
In the end I had to patch it:
router.get('/:collectionName', function(req, res, next) {
var populationQuery = [];
var paths = req.collection.schema.paths;
for (var path in paths) {
if (paths[path].caster) {
populationQuery.push({path: path});
}
}
req.collection.find().populate(populationQuery).sort('-created_on').exec(function (e, results) {
if (e) return next(e);
console.log(results);
res.send(results)
});
});
This works, but I guess there should be some better solution
I am coding a basic project manager, nothing fancy. I am writing the page where the project is created (with AngularJS) and am sending all the $scope to /create (the backend is Express.js). The router gets the JSON perfectly, and save it to a local MongoDB without problems.
My problem is that I want to set a message telling that the project was created successfully and send it back to AngularJS. This is my code.
router.js
module.exports = function(app, db) {
app.post('/create', function (req, res) {
var create = require("./../scripts/create")(req, res, db);
console.log(create); //just for testing whether I can receive the message.
});
}
create.js
module.exports = function(req, res, db) {
db.collection('projects').insert(req.body.project, function(err, docs) {
if (err) throw err;
return 'Project created.'; //I want to return this string.
});
};
I don't know how to return something from inside the db.collection.insert's callback function.
So you have to remember that anonymous function calls in JavaScript are not assigned to anywhere. They are passed, and then lost. This is usually why we don't really have return statements in them.
var x = function () { return y }; gives x the value of y but since there is never an assignment of the value of a callback, a return statement is meaningless. Callbacks, no matter if they have a return value, will not give you a value. They may feed that return value up to the function that they were given to, but they are entirely lost to you.
The way to get around this is to do some trickery with the scope. Basically what you want to do is 'bump' the value you want to return up a scope you can assign and then return it there. For example, you can do this:
module.exports = function(req, res, db) {
var stringToReturn;
db.collection('projects').insert(req.body.project, function(err, docs) {
if (err) throw err;
stringToReturn = 'Project created.'; //I want to return this string.
});
return stringToReturn;
};
This will work because the return value gets bound to module.exports, which is in turn bound to the result of
var create = require('./create');
console.log(create('something')) //should log 'Project created.'
Solved!!
Router.js
module.exports = function(app, db) {
app.post('/create', function(req, res) {
var create = require("./../scripts/create")(req, res, db);
});
});
Create.js
module.exports = function(req, res, db) {
db.collection('projects').insert(req.body.project, function(err, records) {
if (err) throw err;
res.send("Project created.");
});
};
Now Angular is receiving the response from the server.
I have the following code to insert data into DB - this code has to be executed in a sequential order
Router JS
module.exports = function(app) {
app.get('/registerUser', function(req, res ) {
objuser.userName = 'testuser';
objuser.password = 'password';
objuser.status = true;
registerUser (objuser ); //calls Business.js
res.OK();
res.end ();
});
}
Business.js
var registerUser = function (objuser )
{
userDB.registerUser (objuser ) ; //calls db.js
};
db.js
exports.registerUser = function (objUser )
{
var User = db.model(strCollectionName, UserSchema );
var objSchema = new User(objUser);
objSchema.save(function (err)
{
if (err)
console.error (err);
else
console.log ("registerUser : Data insertion success.");
});
}
In the db.js Im getting error from Mongo if I try to insert duplicate value. I wan to pass the error message to HTML page to display the same. What should I do? I tried
throw Error (err)
But it breaks the server.
Assuming you are using expressjs, I'd make use of the next callback. like so:
app.get('/registerUser', function (req, res, next) {
objuser.userName = 'testuser';
objuser.password = 'password';
objuser.status = true;
registerUser(objuser, function (err) {
if(err) {
//this will be handled by express's errorHandler or whatever you have configured
return next(err);
}
//do whatever you want to do with the response
});
});
If you don't want to make your Business.js call async then you will obviously change this code to a try...catch flow. Node.js apps are happier using async calls though, so a common convention in nodejs apps is to expose a callback using the (err, result) parameters. So your db.js call would be :
exports.registerUser = function (objUser, callback )
{
var User = db.model(strCollectionName, UserSchema );
var objSchema = new User(objUser);
objSchema.save(function (err) {
if (err) return callback(err);
return callback(null, objSchema);
});
}
By now you probably notice that your Business.js call would just be a mediator between your route and your db code...whether you need it or not is up to you.
HTH,
Mike
If you are using node.js >= 0.8.x you can use connect-domain middleware that adds new domain functionality to your express/connect application. With doamin module you don't need to pass error up manually. You can simple throw error and it will be passed to error handler automatically.