completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

try, catch & finally

Catch runtime errors, inspect real Error objects, rethrow failures you cannot handle, and use finally without accidentally hiding a return or error.

By the end, you can
  • 01
    Follow the three blocksSay which of try, catch, and finally run, and in what order.
  • 02
    Read Error objectsUse name, message, stack, and cause on a caught error, and check it with instanceof.
  • 03
    Handle only what you understandLeave out the catch name when you don't need it, throw again the errors you can't deal with, and keep finally for cleanup.

Catching an error on purpose

JavaScript runs your code one statement after another. Sometimes a statement fails. Then JavaScript throws an error: it stops the current work and hands the error back to the code that called it. If no code deals with the error, the program stops and the console shows a red message.

You learned to read those messages in Reading error messages. In The call stack, you saw an error leave one function call after another. This lesson shows how to deal with an error on purpose, so your program can keep going. You use three blocks: try, catch, and finally.

The short version

A try statement has up to three blocks:

  • try holds the code that might fail.
  • catch runs only if that code throws. It gets the error, so you can deal with it.
  • finally runs at the end, whether there was an error or not. You use it to clean up.
Real-life analogyA safety net under a ladder

You stand on a ladder to change a light bulb. There is a safety net under the ladder. If you slip, the net catches you, and you are fine. When you are done, you put the ladder away. You do that whether you slipped or not.

In real life: Changing a light bulb on a ladder
In JavaScript: The code inside try
In real life: Slipping off the ladder
In JavaScript: An error is thrown
In real life: The safety net catches you
In JavaScript: The catch block runs
In real life: Putting the ladder away
In JavaScript: The finally block runs

Use try and catch when you know something might fail, and you know what to do if it does. For example:

  • Reading text as JSON, a common text format for data. The text might be broken.
  • Reading data saved in the browser. It might not be there.
  • Checking a form. The user might type something wrong.

Don't wrap every line in try "just in case". A catch that hides errors you didn't expect makes bugs harder to find.

try, catch & finally

STEP THROUGH

Here is the whole idea in one small program. The path array keeps a list of the blocks that ran, in order. Pick what happens inside try: it works, or it throws one of two errors. Then step through the run.

Follow the path through try, catch, and finally
Step 0 of 7Ready
Your turn: follow the blue line

Pick what happens inside try, guess the path, then step through the real run.

Running in
  1. script
Next: line 5
function riskyFormat(text) {  return text.toUpperCase();} let value = "not set"; try {  path.push("try");  value = riskyFormat("ok");} catch (error) {  path.push("catch " + error.name);  value = "fallback";} finally {  path.push("finally");} console.log(path.join(" → "));console.log(value);
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
Choose what happens inside try

Changing this starts the run again. Guess the printed path before you step.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

First, the case where nothing goes wrong. try runs first. catch is skipped. finally still runs at the end.

No error: try, then finallyPop out in the code editor (opens in a new tab)JavaScript
try {
  console.log("try");
} catch {
  console.log("catch");
} finally {
  console.log("finally");
}

Line 2 prints try. Nothing throws, so the catch block on lines 3 and 4 never runs. Line 6 prints finally. The output is try, then finally.

Now a case where a line throws. JavaScript skips the rest of try, runs catch, and then runs finally.

An error: try, catch, then finallyPop out in the code editor (opens in a new tab)JavaScript
try {
  console.log("try");
  throw new Error("sold out");
} catch (error) {
  console.log("catch: " + error.message);
} finally {
  console.log("finally");
}

Line 2 prints try. Line 3 uses throw to throw an error on purpose. new Error("sold out") makes an error object whose message is sold out. JavaScript stops try right there and jumps to catch on line 4. The variable error now holds the thrown error, so line 5 prints catch: sold out. Then line 7 prints finally.

Top: nothing throws. try runs, an arrow jumps over catch, which is skipped, and finally runs. Bottom: line 3 throws. try stops early, an arrow goes to catch, which runs, then to finally, which runs.no errortrycatchfinallyrunsskippedrunsline 3 throwstrycatchfinallystops earlyrunsruns
When nothing throws, JavaScript skips catch. When line 3 throws, it jumps to catch. Either way, finally runs last.
  1. No error: try runs, catch is skipped, and finally runs.
  2. An error: try starts, a line throws, JavaScript jumps to catch, and then finally runs.
  3. Leaving early: finally also runs when try or catch leaves early with return or a new error. You'll see this later in the lesson.

catch only works for errors thrown while the code runs. Some SyntaxErrors happen that way. For example, JSON.parse reads JSON text, and it throws a SyntaxError when the text is broken. A catch can deal with that.

A typing mistake in your own JavaScript file is different. Say you forget a closing brace, }. Then JavaScript can't read the file at all. No line runs, so the try never starts.

Show a message instead of stoppingPop out in the code editor (opens in a new tab)JavaScript
const cart = { total: 12 };

try {
  if (cart.total > 10) throw new Error("Limit reached");
  console.log("checkout");
} catch (error) {
  console.log("show message: " + error.message);
}

Line 1 makes a cart whose total is 12. On line 4, 12 is more than 10, so the if throws an error with the message Limit reached. Line 5 never runs. The catch block prints show message: Limit reached, and the page keeps working.

The built-in error types

INTERACTIVE

When JavaScript throws an error, the thrown value is usually an Error object. It has three properties you'll use most:

  • name is the kind of error, like TypeError.
  • message says what went wrong, in words.
  • stack is text that shows where the error was made, and which function calls led there. Each browser writes it a little differently.

JavaScript has several built-in kinds of error. Read each row of the table as: this kind of error, what usually causes it, and a short example.

The built-in kinds of error
Kind of errorWhat usually causes itA short example
SyntaxErrorThe code can't be read, or JSON text is broken.JSON.parse("{broken}") inside try
ReferenceErrorYou used a name that doesn't exist here.Reading missingValue when it was never made
TypeErrorA value is the wrong kind for what you did with it.null.trim(), because null has no methods
RangeErrorA number is outside the allowed range.new Array(-1), because a list can't have -1 items
URIErrorA web address has a broken % code in it.decodeURIComponent("%")
EvalErrorAn old kind of error. Modern JavaScript almost never throws it.You mostly see it in old documents.
AggregateErrorSeveral errors are reported together, as one.Promise.any(...) makes one when every promise fails.

Try the playground below. Each button throws a real error in your browser and catches it straight away. Then you can see the properties your browser filled in.

Look inside a real Error object
Catch an error and look insidePop out in the code editor (opens in a new tab)JavaScript
try {  JSON.parse("{broken json}");} catch (error) {  console.log(error.name);  console.log(error.message);  console.log(error.stack?.split("\n")[0]);  console.log(error instanceof Error);  console.log(error.cause);}
What your browser madenot run yet

Run the example to see name, message, stack, instanceof, and cause.

Try it yourself

Pick a kind of error, then run it. The code catches the error and shows its properties from your browser.

The playground fills in the stack only when you click, so you see what your own browser writes.

An error can also have a cause property. You add it yourself when you make a new error because of an older one. The new error gets a clearer message. cause keeps the older error, so the first reason isn't lost.

Keep the first error in causePop out in the code editor (opens in a new tab)JavaScript
function readSettings() {
  throw new SyntaxError("bad JSON");
}

try {
  readSettings();
} catch (cause) {
  const error = new Error("Could not load settings", { cause });
  console.log(error.message);
  console.log(error.cause.name);
}

Line 2 throws a SyntaxError. Line 7 catches it in a variable called cause. Line 8 makes a new error with a clearer message. The { cause } part is short for { cause: cause }: it keeps the first error inside the new one. Line 9 prints Could not load settings, and line 10 prints SyntaxError.

Check that you caught an Error

JavaScript lets you throw any value, not just an Error object. So check what you caught before you read message. error instanceof Error is true when error is an Error object. The Checking types reliably lesson explains instanceof and Error.isError.

You can throw any valuePop out in the code editor (opens in a new tab)JavaScript
try {
  throw "offline";
} catch (error) {
  console.log(typeof error);
  console.log(error);
}

Line 2 throws the text "offline". That is allowed, even though it isn't an Error object. Line 4 prints string, and line 5 prints offline. A string has no name or message, so check what you caught before you read them.

Optional catch binding

In catch (error), the name error is called the catch binding. It is the variable that holds the thrown value. Sometimes you don't need that value. You only need to know that something failed. Then you can leave the name out and write catch { ... }. This is called optional catch binding.

Leave out the name when you don't need the errorPop out in the code editor (opens in a new tab)JavaScript
try {  JSON.parse(settingsText);} catch {  settings = defaultSettings;}

Line 2 reads the settings text as JSON. If that throws, line 4 uses the default settings instead. Line 3 has no name in brackets, because every failure gets the same default.

Keep the name, catch (error), when you need the error. For example, when you want to:

  • Print or log the error.
  • Check error.name.
  • Show a message that depends on the error.
  • Throw the error again.
Don't hide useful errors

Leaving out the name is not always better. Do it only when you would never use the error.

Rethrowing errors you cannot handle

CODE PATTERN

A catch block should only deal with errors it understands. Other errors should go on to code that can deal with them. To pass an error on, you throw it again from inside catch: throw error. This is called rethrowing.

Real-life analogyThe small net and the bigger net

Picture a small net under the ladder, and a bigger net below it. The small net is made for one kind of slip. If something else falls into it, the small net doesn't keep it. It drops it into the bigger net, which knows what to do.

In real life: The small net catches the slip it was made for
In JavaScript: catch deals with a ValidationError
In real life: Something else falls into the small net
In JavaScript: A different error, like a TypeError
In real life: The small net drops it into the bigger net
In JavaScript: throw error passes it to the calling code
Deal with one kind of error, throw the rest againPop out in the code editor (opens in a new tab)JavaScript
class ValidationError extends Error {} function saveName(name) {  try {    validateName(name);    return "Saved";  } catch (error) {    if (!(error instanceof ValidationError)) {      throw error;    }    return "Please enter a name.";  }}

Line 1 makes a new kind of error, ValidationError, for bad form input. extends Error means it is a kind of Error. Line 5 calls validateName, a function that isn't shown here. It checks the name and may throw. Line 8 asks: is this error a ValidationError? If not, line 9 throws it again. Only a ValidationError reaches line 11, which returns a friendly message.

A catch card has two arrows. For a ValidationError, the arrow goes to the answer Please enter a name. For any other error, the arrow goes to throw error, which passes the error on to the caller.catch (error)ValidationError"Please enter a name."throw errorany other errorcallergets it
The catch answers only the error it knows, a ValidationError. Any other error is thrown again, so the caller can deal with it.

This lesson keeps ValidationError as small as possible. The next lesson, Throwing & custom errors, covers throw, making your own kinds of error, and cause in more depth.

finally and return

STEP THROUGH

finally runs just before JavaScript leaves the try statement. That happens in four cases:

  • try finishes with no error.
  • catch deals with an error.
  • An error leaves, and no catch stops it.
  • try or catch runs return.

There is one trap. If finally itself runs return or throw, that replaces what was already happening. The earlier return value or error is lost. Step through these three puzzles, and guess each result before you step.

Three puzzles about finally
Step 0 of 7Ready
Your turn: follow the blue line

Guess each puzzle before you step. finally always runs. A return or throw inside it replaces what was already happening.

Running in
  1. script
Next: line 28
function returnFromTry() {  try {    return "try value";  } finally {    return "finally value";  }} function throwThenFinallyThrows() {  try {    throw new TypeError("try failed");  } finally {    throw new RangeError("cleanup failed");  }} function returnStillCleansUp() {  const steps = [];  try {    steps.push("try");    return steps.join(" → ");  } finally {    steps.push("finally");    console.log(steps.join(" → "));  }} try {  throwThenFinallyThrows();} catch (error) {  console.log(error.name + ": " + error.message);}console.log(returnStillCleansUp());
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
finally still runs before a returnPop out in the code editor (opens in a new tab)JavaScript
function saveDraft() {
  try {
    return "saved";
  } finally {
    console.log("stop spinner");
  }
}

console.log(saveDraft());

Line 3 gets ready to return "saved". Before the value reaches the caller, finally runs, and line 5 prints stop spinner. A spinner is the turning icon a page shows while it waits. Then line 9 prints saved.

A return in finally replaces the try returnPop out in the code editor (opens in a new tab)JavaScript
function chooseMessage() {
  try {
    return "saved";
  } finally {
    return "failed";
  }
}

console.log(chooseMessage());

Line 3 gets ready to return "saved". Then finally runs, and line 5 returns "failed" instead. The "saved" value is lost, so line 9 prints failed. This is a common mistake.

Left: saveDraft. try returns saved, then finally logs stop spinner, and the result is saved. Right: chooseMessage. try returns saved, then finally returns failed, and the result is failed.saveDraft()return "saved"finallylogs stop spinnerresult: "saved"chooseMessage()return "saved"finallyreturns "failed"result: "failed"
Both try blocks return "saved", and finally runs before the caller gets it. On the right, the return in finally replaces "saved" with "failed".
A simple rule for finally

Use finally only to clean up. For example:

  • Close a file or a connection you opened.
  • Stop a loading spinner.
  • Put back a setting you changed for a moment.

Don't return from finally. Don't throw from it either, unless the cleanup error is the one you want the caller to see.

What try/catch cannot catch

A catch block only catches errors thrown while its try block is running. Two cases surprise people, because the error happens outside that time. Read each row of the table as: in this case, does this catch run, and why?

What this catch can and can't catch
What happensDoes this catch run?Why
JSON.parse(badText) is inside try.Yes.The error is thrown while try is running.
The same file has a typing mistake, like a missing brace.No.JavaScript can't read the file, so no line runs at all.
A setTimeout callback throws later.No.The callback runs after this try has already finished.
The timer callback has its own try/catch.Yes.That inner try is running when the error is thrown.

setTimeout(fn, 0) asks JavaScript to run the function fn later, after the current code finishes. A function you hand to other code, to run later, is called a callback.

A timer's error escapes the outer catchPop out in the code editor (opens in a new tab)JavaScript
try {
  setTimeout(() => {
    throw new Error("late error");
  }, 0);
} catch {
  console.log("outer catch");
}
console.log("try finished");

Line 2 only schedules the callback. It doesn't run it yet. So try finishes with no error, and line 8 prints try finished. Later, the callback runs, and line 3 throws. The outer catch is already finished, so nothing catches the error. The console shows it as an uncaught error: Error: late error.

The outer try/catch runs first and prints try finished. An arrow marked later leads to the callback card. The callback throws Error: late error, and nothing catches it.outer try/catchprints firsttry finishedlatercallbackError: late errornothing catches it
The outer try and catch are already finished when the callback runs. So the callback's error has no catch around it.

If you wrote the callback yourself, put a try/catch inside it.

The callback needs its own try/catchPop out in the code editor (opens in a new tab)JavaScript
try {  setTimeout(() => {    try {      throw new Error("late");    } catch (error) {      console.log("callback caught itself");    }  }, 0);} catch (error) {  console.log("outer catch ran");}console.log("try finished");

Line 12 prints try finished first. Later, the callback runs. Its own try on line 3 is running when line 4 throws, so the catch on line 5 deals with it. Line 6 prints callback caught itself. A later lesson on promises and async/await shows more ways to handle errors in code that runs later.

await keeps the catch workingPop out in the code editor (opens in a new tab)JavaScript
async function loadProfile() {
  try {
    await Promise.reject(new Error("Profile unavailable"));
  } catch (error) {
    console.log(error.message);
  }
}

loadProfile();

await is different from a timer. A promise is a value that arrives later. Promise.reject(...) makes a promise that fails with an error. On line 3, await pauses the async function until the promise is done.

When the promise fails, the function goes on from line 3 and throws the error right there, inside try. So the catch on line 4 deals with it, and line 5 prints Profile unavailable. A callback that throws later still needs its own try/catch.

Which blocks run?
  • No line in try throws.
  • Line 10, inside try, throws a TypeError.
  • The same file has try { with no closing brace.
  • A setTimeout callback throws after try has finished.
  • try throws a RangeError, and catch returns a default value.
  • try runs return with a value.
  • JSON.parse works inside try.
  • The timer callback has its own try/catch around the line that throws.
Try it yourself
0 of 8 correct

Put each case in the group that says which blocks run.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Where you will use this

Good try blocks are small. Put only the line that might fail inside try. Deal with the error you expect. Leave the rest of the function as normal code.

  • Reading JSON: use a default value when the JSON text is broken, so a settings screen still opens.
  • Checking input: show a friendly message when a user types something wrong. Throw any other error again.
  • Cleaning up: use finally to reset the page after work that might fail. For example, stop a spinner.
  • Finding bugs: when you can't fix an error where it happens, log its name, message, and stack.
A safe way to read JSON

In Exercise 2 you write this yourself. parseOrDefault(text, fallback) returns the parsed data when the text is good. When JSON.parse throws, it returns fallback, a default value instead. Every parse error gets the same default, so catch { ... } with no name fits here.

Give bad settings a friendly defaultPop out in the code editor (opens in a new tab)JavaScript
function readSettings(text) {
  try {
    return JSON.parse(text);
  } catch {
    return { theme: "light" };
  }
}

console.log(readSettings("{broken}").theme);

The text {broken} is not valid JSON, so line 3 throws a SyntaxError. The catch on line 4 returns a light theme instead. Line 9 prints light, and the settings screen still works.

One try inside anotherPop out in the code editor (opens in a new tab)JavaScript
try {
  try {
    JSON.parse("{broken}");
  } catch {
    console.log("inner fallback");
    throw new Error("settings unavailable");
  }
} catch (error) {
  console.log("outer report: " + error.message);
}

A try can sit inside another try. Line 3 throws a SyntaxError. The inner catch on line 4 deals with it: line 5 prints inner fallback, and line 6 throws a new error with a clearer message. The outer catch on line 8 gets that new error, and line 9 prints outer report: settings unavailable.

Put one try inside another only when each one has a different job.

Common mistakes

"catch fixes the error for you."

No. catch only gives you a chance to respond. You still choose what to do: use a default value, show a message, log the error, or throw it again.

"finally runs only after catch."

No. finally runs whether catch ran or not. It even runs when try leaves early with return or an error.

"A try around setTimeout catches errors in the callback."

No. The callback runs after the outer try has finished. Put a new try/catch inside the callback. A later lesson on promises and async/await shows more ways.

"Every thrown value is an Error object."

No. Most built-in errors are Error objects, but JavaScript lets you throw any value, even a string. Check what you caught before you read its properties.

"A return in finally is a tidy way to clean up."

No. It is usually a bug, because it replaces the return value or the error from try. Clean up in finally, but don't return from it.

Practice: catching the right errors

5 EXERCISES

Try each exercise on your own first. Use the hints only if you get stuck.

Exercise 1 · Warm-upPredict which blocks run

The program adds a word to events each time a block runs. The last line joins the words, with " > " between them. Type the exact line it prints.

Starter codePop out in the code editor (opens in a new tab)JavaScript
const events = [];
try {
  events.push("try");
  throw new TypeError("boom");
} catch (error) {
  events.push(error.name);
} finally {
  events.push("finally");
}
console.log(events.join(" > "));

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeWrite a safe JSON reader

    Write parseOrDefault(text, fallback). It should return the parsed JSON. If parsing fails, it should return fallback.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    function parseOrDefault(text, fallback) {
      try {
        return JSON.parse(text);
      } catch {
        return fallback;
      }
    }
    console.log(parseOrDefault("{\"count\":2}", { count: 0 }).count);
    console.log(parseOrDefault("broken", { count: 0 }).count);

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeFix a finally that hides a return

      The function should return "Ada", but finally replaces it. Change finally so the first return value gets through.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      function chooseName() {
        try {
          return "Ada";
        } finally {
          return "Grace";
        }
      }

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · ChallengeThrow again what you can't handle

        Keep the friendly message for a ValidationError, but don't hide any other error.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        class ValidationError extends Error {}
        function readAge(value) {
          try {
            const age = Number(value);
            if (Number.isNaN(age)) throw new ValidationError("Age must be a number");
            if (age < 0) throw new RangeError("Age cannot be negative");
            return "Age: " + age;
          } catch (error) {
            if (!(error instanceof ValidationError)) throw error;
            return "Use digits only.";
          }
        }
        console.log(readAge("ten"));

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeExplain the timer surprise

          Why doesn't the outer catch deal with the late error? Type which catch does.

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          try {
            setTimeout(() => {
              try {
                throw new Error("late");
              } catch {
                console.log("callback caught itself");
              }
            }, 0);
          } catch {
            console.log("outer catch");
          }
          console.log("try finished");

          Answer, then press Check. Spacing and letter case don’t matter.

            Quiz: check your understanding

            7 QUESTIONS

            Pick an answer first. Then read the explanation, even when you were right.

            Lesson quiz · 7 questionsScore: first tries count
            1. Question 1 of 7When does a catch block run?

              Choose an answer to see the explanation.

            2. Question 2 of 7What does this code print? Nothing in try throws.

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              try {
                console.log("A");
              } catch {
                console.log("B");
              } finally {
                console.log("C");
              }

              Choose an answer to see the explanation.

            3. Question 3 of 7Which property usually gives the kind of error, such as TypeError?

              Choose an answer to see the explanation.

            4. Question 4 of 7What does this code print? Both try and finally return.

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              function puzzle() {
                try {
                  return "try";
                } finally {
                  return "finally";
                }
              }
              console.log(puzzle());

              Choose an answer to see the explanation.

            5. Question 5 of 7When is optional catch binding, catch { ... } with no name, a good choice?

              Choose an answer to see the explanation.

            6. Question 6 of 7Why throw an error again from catch?

              Choose an answer to see the explanation.

            7. Question 7 of 7What does this print first?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              try {
                setTimeout(() => console.log("timer"), 0);
              } catch {
                console.log("caught");
              }
              console.log("done");

              Choose an answer to see the explanation.

            Key takeaways

            • try holds code that might fail. catch runs only if that code throws, and it gets the error.
            • finally runs just before JavaScript leaves the try statement, even after a return or an error.
            • An Error object usually has name, message, and stack. Some also have cause.
            • Write catch { ... } with no name only when you never use the error.
            • Throw again any error your catch can't deal with properly.
            • try can't catch a typing mistake in the same file, or an error thrown later in a timer callback.

            Remember the one-liner.
            try is the work that might fail, catch is what you do if it fails, and finally is the cleanup that always runs at the end.

            Up next: Throwing & custom errors.

            CompleteFrontend Clear concepts. Working examples.