Catching Errors in Javascript

It is a normal situation when you write code and no one can make it perfectly. So, Errors must have happened. However, we have to know how to catch the error and where is the error.

When does it happen?

In Javascript(JS), we can classify it into two classes:

  1. Programming error:
    For example, the syntax error, typer error, or some variables are undefined, etc.

  2. Operational error:
    This error is from the system, like the failure from reading the file, or the connection is unusual, etc.

Error object in JS

In MDN, there are six types of errors as following:

  1. Eval Error:
    It occurs regarding the global function eval().

  2. Range Error:
    It occurs when a numeric variable or parameter is out of range.

  3. Syntax Error:
    It occurs when writing the wrong syntax in your code.

  4. Reference Error:
    You take the invalid ref

  5. Type Error:
    The variable or parameter is not of a valid type.

  6. URI Error:
    Pass the wrong parameter when using the method of encodeURI() or decodeUR().

So, it has an error object in JS to deal with these error types. And we can use this object to create an instance to catch a generic error by the following:

try {
  throw new Error("Whoops!");
} catch (e) {
  console.error(`${e.name}: ${e.message}`);
}

Error handle in asynchrony

As an example from above, it uses to catch by the synchronous function. if we write the asynchronous function, it will have no effect.

try {
  setTimeout(() => {
    throw new Error('asyncError');
  }, 0)
} catch(err) {
  console.log(err);
}

// Uncaught Error: asyncError

The asynchronous operation is implemented by an even-loop mechanism. It means that the stack already has been executed when the callback function in the event queue is executed. So, we have to use other methods to catch it.

Error-first-callback

We can set the first parameter to the error object in the callback function as following:

router.get('/signin', (err, req, res) => {
  if (err) {
     console.log(err)
  }
})
Error in promise

With the advent of the Promise standard, it provided the handling method Promise.prototype.catch for error objects.

User.findOne({ where: { email: req.body.email } })
      .then(user => {
        if (user) throw new Error('Email already exists!');
        ...
      })
      .then(hash =>
        User.create({
         ...
        })
      )
      .then(() => {
        ...
      })
      .catch(err => next(err))

async/await

It shows at ES7 and can write as following:

async function test() {
  try {
    await fetch('http://www.google.com');
  }catch(err) {
    console.log(err);
  }
}

test();

it is better to use the async//await when we use the try…catch… to catch the error. Async/await is a syntax sugar for Promise. So it also can be converted to the Promise syntax.

Handling different kinds of errors

When we get some different kinds of requests and we use try .. catch.. to catch these errors, maybe we can write as following:

try {
  await request1()
  await request2()
  await request3()
} catch (err) {
  // error handling
}

When errors happened, we don’t know which request occurs. In order to identify it, we can refactor the code by following two methods:

(1)

async function test(){
  try {
    await request1()
  } catch(err1) {
    // Handling err1 
  }

  try {
    await request2()
  } catch(err2) {
    // Handling err2 
  }

  try {
    await request3()
  } catch(err3) {
    // Handling err3 
  }
}

(2)

try {
  await request1() // throw Error1
  await request2() // throw Error2
  await request3() // throw Error3
} catch (err) {
  if (err instanceof Error1) {
    // Handling the error1
  } else if (err instanceof Error2) {
    // Handling the error2
  } else if (err instanceof Error3) {
    // Handling the error3
  } else {
    // Handling other error
  }
}

However, it is unreadable in method(1) when the code is getting longer and longer. Then, you have to customize your error and analyze the code of error at the request in method (2). It will take your time to read all of the customized errors.

The different ways of error handling

Fortunately, some people find the solution when they write the Go-Lang and find out how they handle the error.

(ref: How to write async/ await without try-catch blocks in JS)

data, err := db.Query("SELECT ...")
if err != nil { return err }

So we can write as following if it is used in Node.js.

async request1() {
    try {
        // request1 process
        return [result1, null]
    } catch (err1) {
        // err1 handling
        return [null, Error1]
    }
}

async request2() {
    try {
        // request2 process
        return [result2, null]
    } catch (err2) {
        // err2 handling
        return [null, Error2]
    }
}

const [result1, Error1] = await request1()
if (Error1) {
  // Error1 handling
}

const [result2, Error2] = await request2()
if (Error2) {
  // Error2 handling
}

The reason for refactoring this code is that await is waiting on a promise to resolve. And the request can be a promise object. So, this author makes a small utility function to help to catch the errors as following:

(ref: How to write async/ await without try-catch blocks in JS, npm package: await-to-js)

// to.js
export default function to(promise) {
   return promise.then(data => {
      return [null, data];
   })
   .catch(err => [err]);
}

While we have this concept, we can refactor the code as following after installing await-to-js which the author developed. Now, we don't need to write the async function and make them clear.

import to from 'await-to-js';

const [result1, Error1] = await to(request1())
if (Error1) {
  // Error1 handling
}

const [result2, Error2] = await to(request2())
if (Error2) {
  // Error2 handling
}

const [result3, Error3] = await to(request3())
if (Error3) {
  // Error3 handling
}

Conclusion

Error handling is an important thing in development. We have to know what kind of error will be occurred or how to catch them. By using the error object and customizing the error with Promise, async/await, it helps us to check the error during development. It is hard to write a well or clean code for error handling even by using the Promise, async/await. However, it is necessary to learn this knowledge and understand the concept of error handling. Now people try to find a solution to make the code to be more readable, like the package of await-to-js. Therefore, keeping learning the basic concept of technique and improving the code is very important.

Reference:

https://blog.grossman.io/how-to-write-async-await-without-try-catch-blocks-in-javascript/

https://engineering.udacity.com/handling-errors-like-a-pro-in-typescript-d7a314ad4991

https://eyesofkids.gitbooks.io/javascript-start-from-es6/content/part3/error.html

https://ithelp.ithome.com.tw/articles/10230537

https://pjchender.blogspot.com/2017/12/js-error-handling.html

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Control_flow_and_error_handling#exception_handling_statements

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error

https://ithelp.ithome.com.tw/articles/10241761

https://mainpage.github.io/2020/07/05/error-and-exception/

https://blog.csdn.net/pfourfire/article/details/124794091

https://www.casper.tw/development/2021/09/14/js-console/