Making better use of the Error Class in Node.Js
Node.js as a backend platform has gained huge traction and apart from all the technical benefits it provides, I personally feel it’s the simplicity with which we achieve a lot more with a lot less that gives it the edge.
In this article, I would like to touch upon an often overlooked topic of the Error class in Node.js. In my experience as a reviewer of Node.js code, I have seen even experienced devs making trivial errors when throwing an error.
function breakPromise() {
return new Promise((resolve, reject) => {
reject('something');
});
}
breakPromise()
.catch(e => {
console.log(e);
});
--------------------------
Result of console.log
something
Consider the above piece of code. We are rejecting a promise using a string. The console.log statement in the catch block will just print ‘something’. Imagine what will you do if you were tasked with fixing a bug which simply printed such strings to the log.
We can remedy this situation fairly easily by rejecting/throwing instances of the Error class.
function breakPromiseSmart() {
return new Promise((resolve, reject) => {
reject(new Error('something bad'));
});
}
breakPromiseSmart()
.catch(e => {
console.log(e);
});
-----------------------------
Result of console.log
Error: something bad
at Promise (index.js:3:16)
at new Promise (<anonymous>)
at breakPromiseSmart (index.js:2:12)
at Object.<anonymous> (index.js:7:1)
at Module._compile (module.js:652:30)
at Object.Module._extensions..js (module.js:663:10)
at Module.load (module.js:565:32)
at tryModuleLoad (module.js:505:12)
at Function.Module._load (module.js:497:3)
at Function.Module.runMain (module.js:693:10)
Clearly, now we have much more information to work with and thus we should never throw string errors.
Errors raised in node.js inherit from, or are instances of, the standard Javascript Error class.
Error class provides some useful properties like
- message (the error message)
- name (the name of the error)
- stack (filename, line number, function call stack)
We can make our error logs much more useful by adding properties to the error objects, however, a better way to do it is to create custom error classes by extending the Error class.
class BadRequestError extends Error {
constructor(message, meta = {}) {
super(message);
Error.captureStackTrace(this, BadRequestError);
let proto = Object.getPrototypeOf(this);
proto.name = 'BadRequestError';
this.meta = meta;
}
toString() {
return `${super.toString()} ${JSON.stringify(this.meta)}`;
}
}
function breakPromise() {
return new Promise((resolve, reject) => {
reject(new BadRequestError('something bad', {
expected: 'this',
got: 'that'
}));
});
}
breakPromise()
.catch(e => {
console.log(e);
});
--------------------------------
Result of console.log
{ BadRequestError: something bad
at Promise (index.js:23:16)
at new Promise (<anonymous>)
at breakPromise (index.js:22:12)
at Object.<anonymous> (index.js:30:1)
at Module._compile (module.js:652:30)
at Object.Module._extensions..js (module.js:663:10)
at Module.load (module.js:565:32)
at tryModuleLoad (module.js:505:12)
at Function.Module._load (module.js:497:3)
at Function.Module.runMain (module.js:693:10) meta: { expected: 'this', got: 'that' } }
We can see that by extending the Error class to create BadRequestError, we were able to add much more information to the stack trace and enrich our logs.
The statement Error.captureStackTrace(this, BadRequestError); in short is used to exclude the constructor from the stack trace.
If we create multiple such error classes and throw appropriately we can return proper responses. For example
breakPromise()
.catch(e => {
if (e instanceof BadRequestError) {
res.status(400);
} else if (e instanceof SomeotherError) {
res.status(412);
} else {
res.status(500);
}
return res.send({
message: e.message,
error: e.meta
});
});
Everything I mentioned in the post is very trivial if you care about errors but trust me, not many developers pay careful attention to it. I hope after this refresher, giving first-class preference to errors will be at the top of your mind.
Disclaimer: Please ignore some statements where I have made too simple generalizations.
Nice blog with genuine information. Thanks alot.