Use only the built-in Error object

One Paragraph Explainer

The permissive nature of JavaScript along with its variety of code-flow options (e.g. EventEmitter, Callbacks, Promises, etc) pushes to great variance in how developers raise errors – some use strings, other define their own custom types. Using Node.js built-in Error object helps to keep uniformity within your code and with 3rd party libraries, it also preserves significant information like the StackTrace. When raising the exception, it’s usually a good practice to fill it with additional contextual properties like the error name and the associated HTTP error code. To achieve this uniformity and practices, consider extending the Error object with additional properties, but be careful not to overdo it. It’s generally a good idea to extend the built-in Error object only once with an AppError for all the application level errors, and pass any data you need to differentiate between different kinds of errors as arguments. No need to extend the Error object multiple times (one for each error case, such as DbError, HttpError) See code examples below

Code Example – doing it right

  1. // throwing an Error from typical function, whether sync or async
  2. if(!productToAdd)
  3. throw new Error('How can I add new product when no value provided?');
  4. // 'throwing' an Error from EventEmitter
  5. const myEmitter = new MyEmitter();
  6. myEmitter.emit('error', new Error('whoops!'));
  7. // 'throwing' an Error from a Promise
  8. const addProduct = async (productToAdd) => {
  9. try {
  10. const existingProduct = await DAL.getProduct(productToAdd.id);
  11. if (existingProduct !== null) {
  12. throw new Error('Product already exists!');
  13. }
  14. } catch (err) {
  15. // ...
  16. }
  17. }

Code example – Anti Pattern

  1. // throwing a string lacks any stack trace information and other important data properties
  2. if(!productToAdd)
  3. throw ('How can I add new product when no value provided?');

Code example – doing it even better

Javascript

  1. // centralized error object that derives from Node’s Error
  2. function AppError(name, httpCode, description, isOperational) {
  3. Error.call(this);
  4. Error.captureStackTrace(this);
  5. this.name = name;
  6. //...other properties assigned here
  7. };
  8. AppError.prototype = Object.create(Error.prototype);
  9. AppError.prototype.constructor = AppError;
  10. module.exports.AppError = AppError;
  11. // client throwing an exception
  12. if(user == null)
  13. throw new AppError(commonErrors.resourceNotFound, commonHTTPErrors.notFound, 'further explanation', true)

Typescript

  1. // centralized error object that derives from Node’s Error
  2. export class AppError extends Error {
  3. public readonly name: string;
  4. public readonly httpCode: HttpCode;
  5. public readonly isOperational: boolean;
  6. constructor(name: string, httpCode: HttpCode, description: string, isOperational: boolean) {
  7. super(description);
  8. Object.setPrototypeOf(this, new.target.prototype); // restore prototype chain
  9. this.name = name;
  10. this.httpCode = httpCode;
  11. this.isOperational = isOperational;
  12. Error.captureStackTrace(this);
  13. }
  14. }
  15. // client throwing an exception
  16. if(user == null)
  17. throw new AppError(commonErrors.resourceNotFound, commonHTTPErrors.notFound, 'further explanation', true)

Explanation about the Object.setPrototypeOf in Typescript: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html#support-for-newtarget

Blog Quote: “I don’t see the value in having lots of different types”

From the blog, Ben Nadel ranked 5 for the keywords “Node.js error object”

…”Personally, I don’t see the value in having lots of different types of error objects [in contrast with having only one] – JavaScript, as a language, doesn’t seem to cater to Constructor-based error-catching. As such, differentiating on an object property seems far easier than differentiating on a Constructor type…

Blog Quote: “A string is not an error”

From the blog, devthought.com ranked 6 for the keywords “Node.js error object”

…passing a string instead of an error results in reduced interoperability between modules. It breaks contracts with APIs that might be performing instanceof Error checks, or that want to know more about the error. Error objects, as we’ll see, have very interesting properties in modern JavaScript engines besides holding the message passed to the constructor…

Blog Quote: “Inheriting from Error doesn’t add too much value”

From the blog machadogj

…One problem that I have with the Error class is that is not so simple to extend. Of course, you can inherit the class and create your own Error classes like HttpError, DbError, etc. However, that takes time and doesn’t add too much value [compared to extending it only once for an AppError] unless you are doing something with types. Sometimes, you just want to add a message and keep the inner error, and sometimes you might want to extend the error with parameters, and such…

Blog Quote: “All JavaScript and System errors raised by Node.js inherit from Error”

From Node.js official documentation

…All JavaScript and System errors raised by Node.js inherit from, or are instances of, the standard JavaScript Error class and are guaranteed to provide at least the properties available on that class. A generic JavaScript Error object that does not denote any specific circumstance of why the error occurred. Error objects capture a “stack trace” detailing the point in the code at which the Error was instantiated, and may provide a text description of the error. All errors generated by Node.js, including all System and JavaScript errors, will either be instances of or inherit from, the Error class…