DEV Community

Cover image for Custom exceptions with JS
Damian Cipolat
Damian Cipolat

Posted on

Custom exceptions with JS

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...');

  }

}

Top comments (13)

Collapse
 
tieppt profile image
Tiep Phan

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
  }
}
Collapse
 
khuongduybui profile image
Duy K. Bui
if (!exception)
    throw exception();

Did you mean !!exception?

Collapse
 
danielpdev profile image
danielpdev • Edited

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.

Collapse
 
sqlrob profile image
Robert Myers

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.

Collapse
 
damxipo profile image
Damian Cipolat • Edited

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?

Collapse
 
danielpdev profile image
danielpdev

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.

Thread Thread
 
damxipo profile image
Damian Cipolat

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

Collapse
 
renatoruk profile image
Renato Ruk

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?

Collapse
 
danielpdev profile image
danielpdev

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.

Collapse
 
damxipo profile image
Damian Cipolat

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

Collapse
 
bernardbaker profile image
Bernard Baker

Good article.

Collapse
 
monfernape profile image
Usman Khalil

Loved it.

Collapse
 
okyanusoz profile image
okyanusoz

Thanks!