Error Handling in JavaScript along with Promises

Error Handling in JavaScript along with Promises

Learn about handling exceptions during the runtime along with its potential danger when working with Asynchronous code

Β·

7 min read

Errors are like shooting yourself in the foot without realizing it. Exception handling, also known as error handling is just about handling a potential breakage of your code.

Try-Catch block

It’s just saying try this code and if it throws an error then catch it.

try {
  console.log("🀑 I am perfect");
} catch (e) {
  console.log(e);
}

console.log("βœ… Everything worked out fine");

// Output:
// 🀑 I am perfect
// βœ… Everything worked out fine

Here, inside the try block, we put code that has the potential of throwing an error. If it doesn’t throw an error then the catch block will not be executed and we move to the next piece of code. But if it throws an error then we will jump directly to the catch block executing the code inside it and then moving to the next piece of code.

try {
  throw new Error("πŸ”« You have been shot!");
} catch (errorInfo) {
  console.log(errorInfo);
  console.log(errorInfo.message);
}

console.log("βœ… Everything worked out fine");

The output will be:

Screenshot_2022-09-05_at_5.08.14_PM.png

Here, the catch block handles the exception thrown by the throw keyword. The errorInfo variable for the catch block holds the caught exception. We will learn more about throw and Error later in the post.

Using finally

You may want to run some code no matter if there’s an exception raised OR not. In this case, you can use the finally block with try-catch.

try {
  throw new Error("πŸ€– I will take care of Humans");
} catch (err) {
  console.log(err.message);
} finally {
  console.log("πŸ€– I will be back");
}

// Output:
// πŸ€– I will take care of Humans
// πŸ€– I will be back

https://media.giphy.com/media/3oz8xOXLrarARzQgX6/giphy.gif

Raising exceptions

Sometimes you want to raise an exception when conditions are not met. You can do that in JavaScript using the throw keyword.

The syntax to do so is:

throw expression;

The expression can be any valid expression in JavaScript. Some of the examples are:

Screenshot_2022-09-05_at_9.46.03_PM_1.png

Using throw inside a try-catch block

In the following snippet, there is a function named raiseAnError which takes an expression as an argument. This function raises an exception using the throw keyword and handles it using try-catch and then in the catch block, it prints the err.

const raiseAnError = (expression) => {
  try {
    throw expression; // Raising an error
  } catch (err) {
    console.log(err); // Logging the error
  }
};

raiseAnError("πŸ”« stop now!");    // πŸ”« stop now!
raiseAnError(0);                 // 0
raiseAnError(true);              // true
raiseAnError(false);             // false
raiseAnError([]);                // []
raiseAnError(2 + 2);             // 4
raiseAnError(5 > 10);            // false

Throwing errors using a class

You can use throw and a class to raise errors. This class can be a custom one OR the pre-built JavaScript error classes like Error, SyntaxError, ReferenceError, TypeError, etc… Learn more on types of native errors in this post.

Using pre-built classes

The syntax for this is shown below where the message is the attribute that will be displayed when the error is thrown.

throw new Error(message);
throw new SyntaxError(message);
throw new TypeError(message);
// and soo...

Screenshot_2022-09-05_at_10.09.20_PM.png

You can handle these errors in your code by using the try-catch.

try {
  throw new TypeError("πŸ’” Something went wrong");
} catch (err) {
  console.log(err.name);
  console.log(err.message);
}

// Output:
// TypeError πŸ‘ˆπŸ½ name of the class
// πŸ’” Something went wrong πŸ‘ˆπŸ½ message attribute

You can learn more about using throw <some-object> v/s throw Error in this Stackoverflow post.

Custom classes

Using custom classes allows us to add additional information and functionalities for raising errors as per our needs. An example of this is the CustomError class:

class CustomError {
  constructor(message, tags, priority) {
    this.message = message;
    this.tags = tags;
    this.priority = priority;
  }

  priorityInfo() {
    return `Priority to solve this error is ${this.priority}`;
  }
}

try {
  throw new CustomError("🀑 Custom error", ["error", "custom"], "πŸ”₯ high");
} catch (err) {
  console.log(err.message);
  console.log(err.tags);
  console.log(err.priority);
  console.log(err.priorityInfo());
}

// Output:
// 🀑 Custom error
// [ 'error', 'custom' ]
// πŸ”₯ high
// Priority to solve this error is πŸ”₯ high

https://media.giphy.com/media/2zdnl4CB3OygOHe1kX/giphy.gif

Handling multiple types of errors

You can not use multiple catch blocks to handle multiple types of errors. There is only one try, catch, and finally block respectively. So to handle multiple types of errors you’ve to use if[...else if]...else inside the catch block.

try {
  throw SyntaxError("Syntax error");
} catch (err) {
  if (err instanceof TypeError) {
    console.log("🌈 TypeError");
  } else if (err instanceof SyntaxError) {
    console.log("🌈 SyntaxError");
  } else {
    console.log("πŸ‘» Some unknown error");
  }
}

// Output:
// 🌈 SyntaxError

https://media.giphy.com/media/nVTa8D8zJUc2A/giphy.gif

Error handling when working with Promises

The try-catch is by default synchronous. That means that if an asynchronous function throws an error in a synchronous try-catch block, no error throws.

const asyncCode = async () => {
  throw new Error("🀑 I will show up no matter what!");
};

try {
  asyncCode();
} catch (err) {
  console.log(err.message);
}

The output is:

Screenshot_2022-09-06_at_12.19.04_AM.png

So while working with asynchronous code either use the .then and .catch where you can handle the errors inside the .catch OR await the async function inside a try-catch block where the catch block will handle the error.

const asyncCode = async () => {
  throw new Error("🀑 I will show up no matter what!");
};

// Using .catch() to handle the error
asyncCode().catch((err) => console.log(err.message)); 
// Output: 🀑 I will show up no matter what!

// Using try/catch to handle the error (with Currying)
(async () => {
  try { await asyncCode(); } 
    catch (err) { console.log(err.message); }
})(); 
// Output: 🀑 I will show up no matter what!

Pro-tip for handling errors with async-await

Handling errors in async-await using try-catch can become quite repetitive. Like the one shown below

const asyncCode = async () => {
  throw new Error("🀑 I will show up no matter what!");
};

// Using Currying to execute this code
(async () => {
  try {
    await asyncCode();
  } catch (error) {
    console.log(error.message);
  }

  try {
    await asyncCode();
  } catch (error) {
    console.log(error.message);
  }

  try {
    await asyncCode();
  } catch (error) {
    console.log(error.message);
  }
})();

// Output:
// 🀑 I will show up no matter what!
// 🀑 I will show up no matter what!
// 🀑 I will show up no matter what!

As you can see it is just too much code. The following snippet is a DRY implementation where runAsync handles errors using try-catch and returns an array where array[0] is the result and array[1] is the error.

const asyncCode = async () => {
  throw new Error("🀑 I will show up no matter what!");
};

// This function will handle errors and will return an array
// This array's first value is the `result` & second value is the `error`
const runAsync = async (promise) => {
  try {
    const result = await promise();
    return [result, null];
  } catch (error) {
    return [null, error];
  }
};

(async () => {
  const [result, error] = await runAsync(asyncCode);
  console.log(result, error.message);

  const [result1, error1] = await runAsync(asyncCode);
  console.log(result1, error1.message);

  const [result2, error2] = await runAsync(asyncCode);
  console.log(result2, error2.message);
})();

// Output:
// null 🀑 I will show up no matter what!
// null 🀑 I will show up no matter what!
// null 🀑 I will show up no matter what!

Logging Errors

You can use custom loggers like Winston, etc... to log errors and save them in a log file. This will help you learn more about your errors. You should also explore remote exception handling. Learn more about it in this post.

You can use the console of JavaScript to do assertion checks and log errors using console.assertion and console.error.

  • console.assertion will take the first argument as a condition and if then fails then it will log an error and if it succeeds then it will log nothing
  • console.error will log the message passed to as an error

Screenshot_2022-09-06_at_12.36.10_AM.png

Conclusion

I’ve covered how to raise, handle errors, and work with errors in asynchronous code. If you liked this post then do πŸ’œ like and give your πŸŽ™ feedback.

https://media.giphy.com/media/UkFNy2JFu03D6RHRaP/giphy-downsized-large.gif

Β