Error Handling in JavaScript along with Promises
Learn about handling exceptions during the runtime along with its potential danger when working with Asynchronous code
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:
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
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:
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...
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
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
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:
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 nothingconsole.error
will log the message passed to as an error
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.