Custom exceptions with JS
Damian Cipolat

Damian Cipolat @damxipo

About: Hi, I'm from Argentina. I specialize in Nodejs and microservice technologies over AWS. Thanks for reading me XD

Location:
Argentina
Joined:
Feb 28, 2020

Custom exceptions with JS

Publish Date: Mar 6 '20
85 13

It is very common languages ​​such as java and c # to create custom exceptions, to differentiate error situations from one another. In JS there is the error object and several other types, but they are for very limited uses.

For that reason there is also the possibility of creating our types of exceptions. To simulate that there is a typing for them. We can do this in a 100% OOP or functional way.

Using CLASS:

We can create custom exceptions for our programs, in this case using OOP class in JS.

Demostration code

class ValidationError extends Error {
  constructor(message) {
    super(message)
    this.name = 'VALIDATION_ERROR'
    this.message = message
  }
}

class PermissionError extends Error {
  constructor(message) {
    super(message)
    this.name = 'PERMISSION_ERROR'
    this.message = message
  }
}

class ExecutionError extends Error {
  constructor(message) {
    super(message)
    this.name = 'EXECUTION_ERROR'
    this.message = message
  }
}

module.exports = {
  ValidationError,
  PermissionError,
  DatabaseError
}

How to use it?

function myThrow(input) {

   if (!input)
     throw new ExecutionError('A execution error');

   return input
}

Using FUNCTIONS:

We can create custom exceptions using a functional programming style.

Demostration code

const ValidationError = (message)=>({
  error: new Error(message),
  code: 'VALIDATION_ERROR'
});

const PermissionError = (message)=>({
  error: new Error(message),
  code: 'PERMISSION_ERROR'
});

const ExecutionError = (message)=>({
  error: new Error(message),
  code: 'EXECUTION_ERROR'
});

Example

const {
  ValidationError,
  PermissionError,
  DatabaseError
} = require('./exceptions.js');

function myThrow(input) {

   if (!input)
     throw ExecutionError('A execution error');

   return input
}

Extending the exception methods:
"Serializing Error objects", I use function to can use the constructor later.

//Define exceptions.
function MyError(message){
  const internal = {
    error: new Error(message),
    code: 'MY_CUSTOM_ERROR'
  };

  return {
    ...internal,
    toJSON:()=>({
      code: internal.code,
      stack: internal.error.stack,
      message
    })
  }
}

MyError.prototype = Object.create(Error.prototype);

Example:

//Launch
throw new MyError('So bad configuration');

//Capturing.
try{

  //.....
  throw new MyError('So bad configuration');  
  //.....

} catch(err){
  console.log('Error',err.toJSON());
}

Real life examples:

Having read the previous examples, it is time to find real examples of exceptions that we could create. I am going to propose some that I have used in past projects.

HTTP error codes

  • Bad Request
  • Unauthorized
  • Not Found
  • Internal Server Error
  • Bad Gateway
//Definif exceptions.
const BadRequest = ()=>({
  message: 'Bad Request',
  code:400
});

const Unauthorized = ()=>({
  message: 'Unauthorized',
  code:401
});

const NotFound = ()=>({
  message: 'Not Found',
  code: 404
});

const InternalServerError = ()=>({
  message: 'Internal Server Error',
  code: 500
});

const BadGateWay = ()=>({
  message: 'Bad Gateway',
  code: 502
});

//Define exceptions map.
const exceptionMap = {
  502: BadGateway,
  500: InternalServerError,
  404: NotFound,
  401: Unauthorized,
  400: BadRequest
};

//Using it.
const getUser = async(userId)=>{

  //Make request, this line is just an example, use a real rest client.
  const response = await fetch('http://user.mock/'+userId);

  //Get httpcode.
  const {
    status,
    body
  } = response;

  //Get the exception.
  const exception = exceptionMap[status];

  if (!exception)
    throw exception();
  else
    return body;
}

Bussiness operations

  • Loan rejected
  • Loan exced
  • Loan pending
//We have this custom exceptions
const LoanRejected = (motive, clientId)=>({
  message: 'The loan was rejected',
  code:'LOAN_REJECTED',
  motive,
  clientId
});

const LoanExced = (clientId)=>({
  message: 'The loan ammount exced the limits',
  code:'LOAN_EXCED',
  clientId
});

const LoanPending = ()=>({
  message: 'The client has a loan pending for payment',
  code:'LOAN_PENDING'
});

//I simulate a loan process.
const processate = async(clientId,ammount)=>{

  const status = await getLoanStatus(clientId,ammount);

  //Detect status to reject the loan.
  if (status.code=='REJECTED')
    throw LoanRejected('Status not ready to calc',clienId);

  if (status.code=='UNAVAILABLE')
    throw LoanRejected('Clien banned',clienId);

  if (status.code=='EXCED')
    throw LoanExced();

  //If the client has debts.
  if (status.code=='PENDING')
    throw LoanPending();

  const loanId = await createLoan(clientId);

  return loanId;

}

//And we detect the type of exceptions.
const loanFlow = async (clientId,ammount)=>{

  try{

    const loanId = procesate(clientId,ammount);
    console.log('New loan create',loanId);

  } catch(err){

    if (err.code.includes['LOAN_REJECTED','LOAN_EXCED','LOAN_PENDING'])
      console.log('Loan rejected!!');
    else
      console.log('Problem in process try again later...');

  }

}

Comments 13 total

  • Tiep Phan
    Tiep PhanMar 6, 2020

    You don't need explicit call this line

    class ValidationError extends Error {
      constructor(message) {
        super(message) // this one will store the message variable to message field
        this.name = 'VALIDATION_ERROR'
        // this.message = message
      }
    }
    
  • Bernard Baker
    Bernard BakerMar 6, 2020

    Good article.

  • Duy K. Bui
    Duy K. BuiMar 6, 2020
    if (!exception)
        throw exception();
    

    Did you mean !!exception?

  • Usman Khalil
    Usman KhalilMar 7, 2020

    Loved it.

  • danielpdev
    danielpdevMar 7, 2020

    Good article!
    It's easy to just throw error and use catch to handle the exception, but ideally you shouldn't use throw very often as it's a huge drawback on performance.

    • Renato Ruk
      Renato RukMar 7, 2020

      Can you explain this in more detail? I guess throwing a lot of exceptions frequently may introduce performance issues, but not sure why. Is it because of the stack tracing?

      • Damian Cipolat
        Damian CipolatMar 7, 2020

        Maybe the problem Is hoy don't catch the exceptoins very well.Seriously, it's the 1st time I've heard that throwing exceptions loses Performance. but I want to see how you base it, I don't say no but I am working like this on a project and we didn't lose performance

      • danielpdev
        danielpdevMar 7, 2020

        Yes, you are right. It's because of the stack tracing. As it needs to build your stack trace and it walks through the call stack to collect all required information like method names, line numbers etc.

    • Damian Cipolat
      Damian CipolatMar 7, 2020

      Well I'm using this way in a very massive production project and we don't lose Performance. Do you have a link that supports this or what you say or experience to tell?

      • danielpdev
        danielpdevMar 7, 2020

        try-catch will impact your performance only if you actually throw exceptions.
        Examples:

        1. Performance loss when exception is thrown: jsperf.com/try-catch-performance-jls
        2. No performance loss if no exception is thrown: jsperf.com/try-catch-performance-j...

        Conclusion:
        I'm not saying that you should not use try-catch at all, but like in the examples above actually throwing an exception can be easily avoided.

        • Damian Cipolat
          Damian CipolatMar 7, 2020

          Nice, thanks for the links provides very useful information. I will be investigating about this.

    • Robert Myers
      Robert MyersMar 7, 2020

      The way I've always heard, and continue to follow, exceptions are for exceptional conditions. They shouldn't be for logic flow.

      In a good many of my projects (all languages), there's plenty of throws, but almost no catches at all, except maybe at or near the top level. In the case of errors, I want things to blow up. If the db goes down, there's not much I can do. If I have a bug that results in a throw, it's noisy and I have a nice stack trace right to it, fail early and noisy.

  • okyanusoz
    okyanusozJul 16, 2021

    Thanks!

Add comment