JavaScript Error Handling with throw, try, and catch

In JavaScript, error handling is an essential aspect of programming. The throw, try, and catch constructs are vital for managing errors and providing a better experience for users and developers alike.

Throw

The throw keyword allows developers to create custom errors and throw an exception.

Technically, we can throw almost anything. For example, if you throw a string, you'll see a message that says "Uncaught whoops!" in the console. Similarly, you'd see an empty object if you threw an empty object.


throw "whoops";
// Uncaught whoops!
throw {};
// Uncaught {}

However, it's most common to throw an Error object that contains a specific message:


throw new Error("invalid data");
// Uncaught Error: invalid data

The most important thing to know about throw is that it will interrupt the flow of the code.

For example, if we have two console.log statements, they will both display as expected:


console.log("FIRST");
console.log("SECOND");

If we throw something in between, only the first thing is printed out. As soon as JavaScript encounters the throw, it stops executing the code.


console.log("FIRST");
throw "lol";
console.log("SECOND");

When the normal flow of the program is interrupted, the control is passed to the nearest error handling mechanism. However, we don't have any error handling mechanism in place right now. That will come later when we discuss try and catch.

Divide Function Example

Consider a function called divide that takes two numbers, a and b and returns the result of dividing a by b. Here's the code for the function:


function divide(a, b) {
return a / b;
}

If we call the function with divide(3, 5), we get the result 0.6.

However, if we try to divide by 0, we get an unexpected result: Infinity.

This behavior is specific to JavaScript, as in many other programming languages, dividing by 0 would cause an error.

To avoid introducing the Infinity value into our code, we can add a simple check to see if b is exactly equal to 0.

Remember, using console.log to print a message is not sufficient as it doesn't interrupt the flow of the program.

We need to use the throw keyword to generate an error and interrupt the flow of the code:


function divide(a, b) {
if (b === 0) {
throw new Error("division by zero is not allowed");
}
return a / b;
}

Now, if we try to divide by 0, our function will throw an error, preventing the Infinity value from being returned.

Handling Errors with try and catch

When we throw an error, we usually want a way to handle it. This is where try and catch statements come into play.

The try Block

The first thing to learn is how to write a try block.

A try block is not function. It is a block that looks similar to writing an if statement.

Inside of the block, you can write code that might result in an error.


try {
divide(3, 0)
}

However, a try block on its own doesn't really give us much. In fact, we'll get an error if we don't have a catch block after the try block.

The catch Block

The catch block comes after our try block, and will handle any error that occurs in the try block.


try {
divide(a, 0);
} catch {
console.log("error occurred");
}

In the above code, when the error occurs in the try block, the catch block handles it by logging "error occurred" to the console.

If the code inside the try block doesn't throw an error, the catch block is skipped, and the code continues executing normally.

The catch block can also take a parameter, which is usually called error or err. This parameter will hold the actual error object that we are catching.

We can then access the message property of the error object using error.message.


function divide(a, b) {
if (b === 0) {
throw new Error("division by zero is not allowed");
}
return a / b;
}
try {
divide(a, 0);
} catch (error) {
console.log("error occurred", error.message);
}

For our divide example, we could get the error message:


error occurred division by 0 is not allowed.

Let's add some code that generates a different error.

For example, we can create a constant variable using const, then try to reassign it:


try {
const x = 10;
x = 22;
} catch (error) {
console.log("error occurred", error.message);
}

Now we get a different error message in the same catch block:


error occurred. Assignment to constant variable.

When an error occurs in a try block, it will be caught by the corresponding catch block. We can then handle the error differently if we want to. In this case, we're just printing the error message.

A Real-World Example

Here's a slightly more complicated example of how you could use error handling in the real world.

We have a basic function called fetchURL that takes a URL and makes a request to it.


async function getUrl(url) {
try {
// Try getting the data from the url
const response = await fetch(url);
const data = await response.json();
console.log("HERE IS YOUR DATA", data);
} catch (error) {
// Handle the error or provide a fallback behavior
console.error("Failed to fetch data:", error.message);
}
}

This request may or may not work, depending on various factors such as the URL being invalid, the website being down, or the device having no internet connection. In such cases, error handling becomes essential to gracefully handle these exceptions

When we call getUrl a valid URL, we get the data back as expected:


getUrl("https://jsonplaceholder.typicode.com/todos/1");
// Output:
// HERE IS YOUR DATA {userId: 1, id: 1, title: "delectus aut autem", completed: false}

Now, let's rerun the code with an invalid URL:


getUrl("https://invalid-url.example.com");
// Output:
// Failed to fetch data: Failed to fetch

This time we get an error: "Failed to fetch data." The error message is printed out using error.message, which was caught in the catch block.

Wrap Up

This error handling workflow is common.

Try something that may or may not cause an error. If it doesn't cause an error, great, everything works. If it does throw an error, catch that error and handle it.

In our examples, we catch these errors and handle them by logging the error message in the console using console.error(), which prints the message in red. In the real world, our error handling logic would probably be a bit more robust.

The big takeaway here is that try, catch, and throw are tools that can help you handle errors in your code effectively.

Transcript

00:00 In JavaScript, error handling is an essential aspect of programming. Throw, try, and catch are vital constructs for managing errors and providing a better user experience plus a better developer experience. We'll talk about all three, but let's start with throw.

00:16 Throw allows us, developers, to create custom errors and throw an exception. Now, technically, we can throw almost anything. I can throw a string, like, whoops. And if I do this in the console,

00:31 you'll see I get what looks like an error message saying, uncut, whoops. I can also throw an object if I wanted to. I'll just throw an empty object. But what's most common is to throw an error. So something like this, throw new error, and then in parentheses, any sort of error message.

00:49 This will make an error object. So maybe I'll do something like, you know, invalid data, very nondescript error, or nondescriptive. But you can see we have a different message. It says, uncut error, invalid data.

01:06 What's most important to know about throw, though, is it will interrupt the flow of my code. So if I have a console.log first, and then I console.log second, and in between, well, I'll just show, you know, both of those should print out.

01:24 But if I throw something in between, throw LOL, only the first thing is printed out. And then as soon as JavaScript encounters the throw, whatever we're throwing, it doesn't really matter. The normal flow of the program is interrupted, and the control is passed to the nearest error handling mechanism,

01:42 which we don't really have any error handling mechanism in place right now. That's later to come. Okay, so typically we will throw an error message. And I have a very simple, honestly contrived example, but I want to keep it simple. I have a function called divide. It takes two numbers. Why would you need a function to do this? You probably wouldn't.

02:00 But imagine we're doing something more complex. I'll show a more complex example later. A and B, two numbers. We'll assume they're numbers. We return A divided by B. If I call divide with, I don't know, how about 3 and 5, I get 0.6. That's correct.

02:18 And if I divide by 0, we get maybe an unexpected result. We get infinity, which is just how JavaScript works when you divide by 0. In many other programming languages, this actually causes its own error. So if I want to replicate that, I find this behavior confusing,

02:34 and I don't want to introduce the infinity value into my code at any point. What I could do is very simple check. If B is exactly equal to 0, instead of just console.logging something, that's not going to stop the flow of this program. Whoops.

02:53 Divided by 0, that's just going to be a little message that's printed out. And then the code keeps running, and we return infinity. Now I could probably gate this off and do an if else, but still, in the case of an error, when I want to generate an error and interrupt the flow of my code, that's not happening here.

03:12 So instead, I can use throw. I'll throw a new error, and I'll say division by 0 is not allowed. And now if I try and divide by 0, if B is exactly equal to 0, we have an error. Division by 0 is not allowed. And then this code never runs.

03:30 It stops the flow of our execution of the code and passes that error off to any error handling, but we don't have any error handling. That leads us to try and catch. When we throw an error, we usually want a way to handle it. This is where try and catch statements come into play.

03:47 The first thing I'll show you is that we write a try block. A try block looks like this. I'll just do it down here. T-R-Y, and then a block of code. So it's not a function, it's a block. Like if we were writing an if statement, for example. We use curly braces to indicate the start and end of the block.

04:05 Inside of this try block, we can write code that might result in an error. So a simple example that we know results in an error is something divided by 0. This divide function where B is 0 will throw a new error as we see over here. But a try block on its own doesn't really give us much.

04:23 In fact, we'll get an error. If I save this code, it says missing catch after try. And that brings us to the catch block. So catch comes after our try block, and it will handle any error if an error occurs in the try block.

04:40 So we know that this will result in an error right there. And then in here, I can handle it however I want. Maybe I'll just do something super simple like console.log error occurred. Okay, and you can see that error occurred is printed out.

04:57 Again, very simple, probably not how we would actually handle errors in the real world, just printing them out to the console. But it allows us to intercept those errors that have been thrown, in this case, this error right here, handle it so that my code doesn't just stop running. Instead, I can do something with that error.

05:16 And then, of course, if I have something that's valid, I'm going to console.log this. If I divide by, I don't know, 2, that's totally valid. We see 1.5 printed out. This code never runs.

05:32 So the catch block only runs if an error was generated within try. Now, if none of this is in a try block, there's no catch. Both of these lines are going to run right now. But if I divide by 0, that throws an error. This does not print out.

05:50 This does not print out because our error was never handled. It stops the execution of our code. So another thing that we can do with a catch block is actually add in a parameter here. Usually it's called error or E or E-R-R. I'll call it error.

06:06 This will hold the actual error, the object that we are catching. And then I can access on that error, error.message. Sorry that this has collapsed onto two lines. Maybe I'm zoomed in too far.

06:21 So whatever this error is, we're catching it, intercepting it, and then handling it. In this case, I'm just saying error occurred, and then I'm printing the error.message, which will be this message here. So let's try it again with dividing by 0, and we get error occurred. Division by 0 is not allowed.

06:39 But then I might have some other code in here that generates a different error. For example, this is super simple, but let's make a variable using const. const x equals 10. And remember, const defines a constant, so I'm not supposed to change it. If I try and change it to 22, now we have a different message printed out.

06:58 This threw its own error. Just JavaScript throws an error when you try and do this. Reassign a constant. We caught it. Any error that occurs in try will be caught right here. And then we have this error object, and we can handle things differently if we wanted to. In this case, I'm just doing the same thing, printing out error occurred with the error.message,

07:17 and it says error occurred, assignment to constant variable. Here's a slightly more complicated example, still simplified, of how you could use this in the real world. So I have a basic function called the getURL, or maybe I should call it fetchURL. It takes a URL, and it makes a request to that URL.

07:34 And it's okay if you're not familiar with fetch and await and async. That doesn't matter. But it's trying to send a request to whatever URL we pass in. And that may work. It may not work. It might be an invalid URL. The website might be down. There might be no Internet on this device. There's a lot of reasons it could fail.

07:51 So we wrap the whole thing in a try block, and then we catch any errors, and we'll console.log any errors. So let me just show an example of this. This is a valid URL. I'm going to call getURL with this valid URL. And it says here's your data, and then it prints out my data. Very simple.

08:10 So this line ran. Nothing went wrong. None of this code runs in the catch block. But now if I rerun it and I totally mess up this URL, this time we get an error. Failed to fetch data. And then it prints out the error message, error.message, that was caught.

08:28 So we caught this error. We tried to make all this code happen, tried to get it to run. At some point, an error was thrown. And then when that was thrown, we caught the error. This is a super common workflow. Try something that may or may not cause an error. If it doesn't cause an error, great, everything works.

08:45 If it does throw an error, then we can catch that error. Now, in this case, I'm not throwing the error myself. I showed an example of that earlier with the divide function. But instead, there might be an error thrown when we try and fetch or when we try and parse the JSON. Things can go wrong. JavaScript itself is throwing those errors.

09:03 And then we can catch them and handle them. And the way I'm handling it for now is very basic. I'm just console.logging the – well, I'm actually console.erroring the error, which is just going to make it print out as red, basically, in the console. But in the real world, our error handling logic would probably be a bit more robust.

09:19 So that's how try, catch, and throw can work together.

More Tips