• JavaScript

Jumping the Call Stack with Try, Catch and Throw

From the class:  The JavaScript Runtime

We're going to talk about three keywords that you probably use all the time in your applications-- the try, catch, and the throw keywords. Normally, when we think about trying and catching, we think about error handling. So in this example, I'm throwing an error up in the oops function, and then that error gets printed out to the console here.

But what we're actually doing when we throw something is we're jumping to a previous point in the call stack. And we can use this knowledge for other use cases other than just error handling. I'm going to show you how it works so that you get a better sense of what's actually happening when you throw something.

I've changed the code up quite a bit. Let me walk you through it. I'm calling the function 1 down at the bottom here, and here's that function. And that function just prints 1 to the console like this, and then calls the second function, or the 2 function. And then function 2 does the same exact thing and calls up to function 3, and then to 5, all the way until we get to 6.

So if we look over in the debugger, we've dropped ourselves right into this debug statement here. And the call stack has our anonymous function where we started, and then functions 1 through 6, and now we're currently paused in line 3 of the function 6.

So what we want to do is to see what happens next when we throw this new error. So I'll step through this. And when we throw, what's actually happening is the stack is unwinding. And so the runtime is going to look first in function 5 and see if there's a catch statement for this, and then into function 4 and function 3.

And it will go all the way down the call stack until it gets to the beginning. And if it's uncaught, then it will throw it into the console like this. So we'll see this error message logged. And that's why we're seeing this, oh goodness, something awful as happened in the console.

What we can do to catch this error is down in function 3, just as an example-- we could have picked any of these functions-- but let's put a try keyword here and then a catch. And we'll catch the error, which we abbreviate with E for error. And I'll just drop us into the debugger here. And we'll go back over to the console and refresh.

OK. So we're back up in Function 6. And notice the call stack over there. We have 1 through 6. And I'll step through. And now when I press Play, notice the call stack unwound. And now we're at function 3 instead of functions 6. But we skipped over functions 5 and 4, because the runtime found the first part of the call stack that had a try catch statement and dropped us into the catch.

So we've just gone from function 5 right here in this throw statement on line 4, and we skipped all the way past down functions 5 and 4 down to function 3, where we have this catch statement. And notice that this return right here will never execute. And so any code that's after this throw will not execute.

So what we've actually done here is we've just been able to unwind the call stack. We jumped to a previous point in the call stack. And now our call stack is up from 1 to 3.

Now, I don't want to actually do any error handing here, but I want to continue execution. So I'm going to step over this and return. Now, look at the call stack. You see that have we 1 and 2, but 3 has been popped off. And now we continue to wind down the call stack until we're at the beginning, and eventually, it's empty, just like our normally running program.

So being able to jump from one point in the call stack or the top of the call stack to a previous point in the call stack is really useful for error handling. But it doesn't have to be an error that we're throwing. We can actually throw any kind of object. So let's change the statement in line 4 to throw some custom object.

So I'll delete line 4. And instead of this, I'll just throw an object literal. And I'll give it a message. And then so that it can print properly, I'll add a two string function that just returns the message. Now, we still have this return down here, but it's not going to do anything. We will never get there, because we're going to throw to a previous point in the call stack.

Now, down here, I'm going to change the catch argument from e to just object, because now we're just throwing any kind of object. Now, we don't have to do this, but I just wanted to make it really clear what's happening. And then why don't we just print out the object to the console.

OK. Here we are up in function 6. And I'll press Play. Now we're going to throw back down the call stack until we find a catch statement. And there we found one. And notice that the object is just a regular object that we created. And here is the object in the console.

And over in the call stack, again, we have 1, 2, and 3. So we've jumped to a previous point in the call stack, which is the function 3. Since we can throw any object, sometimes you'll see a pattern where you can create a static object. So for example, I might create some kind of error here, or just something called My Special Code, or just something with significance. And it will just be an empty object.

And then instead of throwing this, I can throw this special object. And then down in 3, we could conditionally check. If the object is equal to my special code, then go ahead and just print it to the console or do something special. Otherwise, just throw it on down the stack.

So what I'm illustrating here is that we can take this object that we've just caught, and we can go ahead and rethrow it if we want. And so in that case, the object would get thrown from this point in the stack to any previous point in the stack where there's another catch statement. So let's go ahead and try out this code in the console.

Then I'll debug into the next statement and press Play. And here we are down at the bottom. And it's just printing out an empty object now, because we don't have any message or to string defined on it. So to make it a little bit more clear, why don't I write a message. And again, we'll just define a to string function here, which will return the message.

And then I'm going to put a debug statement right after we print this to the console so we can see the call stack. And then over in the console, we'll just refresh and go right on back to where we were. And now we see that the object printed to the console with a message and a to string function. And looking at the call stack there, you can see we're up at call stack at function 3.

So the throw keyword allows us to return to some previous point in the stack-- namely, the previous point in the stack where we have a catch. So this is really useful for error handling. But I also showed you how we can throw custom objects and conditionally decide what to do and whether or not to continue to throw the object down the stack or to do something custom.