Learn by reading through in order

Catching Exceptions — try / catch / finally

Catch runtime exceptions with try and catch, run cleanup in finally, rethrow with throw, and branch by error type.

When you use JSON.parse to turn a received string back into an object, a single wrong character stops execution on that line, and the display and save code you wrote below it never runs. You can't rule out every possible cause ahead of time with if checks, either.

This article covers try / catch, which catches the exception that stopped your code, and finally, which always runs.

Catching an Exception — try and catch

Say you want to parse a settings string received from a server and apply it to the page. If the string is malformed, JSON.parse throws an exception (an error that interrupts processing) on that line, and none of the lines below it run. The page stays blank, and nothing else happens.

When an exception is thrown inside try (a block wrapping code that might fail), execution moves to catch (a block that receives the exception). The error in catch (error) is simply the name you give the received value (err or any other name works just as well). It holds name, the name of the error type, and message, a description of what went wrong.

// A string with a leftover trailing comma
const configText = '{"theme":"dark","fontSize":14,}';

try {
  const config = JSON.parse(configText);
  console.log(config.theme);          // this line doesn't run
} catch (error) {
  // execution jumps here when an exception is thrown
  console.log("Couldn't load the settings");  // Couldn't load the settings
  console.log(error.name);                    // SyntaxError
}

// thanks to the catch, the lines after it still run
console.log("Showing the default settings");   // Showing the default settings
Without try vs. With try
parse withouttryException thrownon that lineNowhere tojump toFollowing linesdon't runparse insidetryException thrownon that lineJumps tocatch (error)Lines after tryrun
Either way, the line right below the one that threw doesn't run. With try, control moves to catch and carries on to the lines after try.

Once execution moves to catch, it never goes back to finish the line that threw, so put your fallback display or default value inside catch. The wording of message varies between browsers, so check name when you need to tell error types apart.

Pull the title out of a saved draft. savedDraft and brokenDraft are already declared.

① In restoreTitle, parse the string and return its title.

② If that fails, print the name of the error type and return "Untitled draft".

③ Call it with both strings and print each result in the form "Title: September report".

(If it runs correctly, an explanation will appear.)

JavaScript / TypeScript Editor

Run code to see output

Running Code Whether It Succeeds or Fails — finally

Cleanup such as hiding a loading indicator or closing an open connection is needed whether the operation succeeds or fails. If you write the same line in both try and catch, you may later fix one copy and forget the other, and the two paths stop behaving the same.

finally (a block that always runs when leaving try) goes after catch. Whether try ran to the end or catch received an exception, it always runs just before leaving. You can also leave out catch and write only try and finally.

function loadReport(text) {
  try {
    const report = JSON.parse(text);
    console.log(report.title);
  } catch (error) {
    console.log("Failed to load");
  } finally {
    // always runs when leaving try
    console.log("Loading indicator hidden");
  }
}

// On success
loadReport('{"title":"September sales"}');  // September sales → Loading indicator hidden

// On failure
loadReport('{"title":}');                   // Failed to load → Loading indicator hidden
Two Paths That End in the Same Place
Pass a validstringtry runs tothe endcatch isskippedfinallyrunsPass a malformedstringException thrownpartway into trycatchreceives itfinallyruns
The top and bottom rows take different routes but end at the same block. finally runs whether the code succeeds or fails.

finally runs after the code in try or catch has finished. That's why, in the code above, the cleanup message appears after the title on success and after the failure message on failure.

Variables Declared Inside try Can't Be Read Outside It

A variable declared with const or let inside try can only be used inside that block. Reading it from catch, finally, or any line after the whole statement throws a ReferenceError. Declare values you'll need later with let before try, and only assign to them inside try.

Import member data one row at a time and show the progress. rows and processed are already declared, and row 2 is malformed.

① Parse each row and print it in the form "Alice: Annual plan".

② If parsing fails, print "Found a row that couldn't be imported".

③ Whether it succeeds or fails, add 1 to processed and print it in the form "Processed 1 / 3".

JavaScript / TypeScript Editor

Run code to see output

Letting the Caller Know — throw Inside catch

Say a function that reads booking data catches an exception and only logs it. The function then returns normally, so the caller never notices the failure, keeps using the return value, and stops again on some other line far from the actual cause.

Passing the error that catch received straight to throw (a statement that throws an exception) sends the same exception on to the caller. The function can log the failure itself and still leave it to the caller to decide how to respond.

function readEntry(text) {
  try {
    return JSON.parse(text);
  } catch (error) {
    console.log(`Can't read the booking data: ${error.name}`);
    throw error;   // rethrow the received exception as-is
  }
}

try {
  const entry = readEntry('{"name":"Bob","seats":}');
  console.log(entry.name);              // this line doesn't run
} catch (error) {
  console.log("Booking canceled");      // Booking canceled
}
Rethrowing vs. Not Rethrowing
catch hasthrow errorSyntaxError ispassed upOuter catchreceives it"Bookingcanceled"catch has nothrow errorReturn value isundefinedTypeError atentry.nameCaller gets adifferent error
In the bottom row, the exception is swallowed inside the function, and the code fails again on the line that reads from undefined. When you rethrow, the original SyntaxError reaches the caller.

If the caller also needs to respond to an exception caught inside a function, rethrow it by writing throw error; at the end of catch. Lines after throw don't run, so put your logging line before it.

An Empty catch Makes the Failure Disappear

Leaving catch (error) { } empty makes the exception seem to vanish, but the failure isn't recorded anywhere, so you can't trace the cause later. The most common result is a page that just stays blank. Always do at least one of these: show a message, fall back to a default value, or rethrow.

Load a saved layout. savedLayout, layout, and errorLog are already declared.

① In loadLayout, parse the string and return the result.

② If that fails, add the error type's name to errorLog and rethrow.

③ Assign the result to layout, and if that fails, print "Showing the default layout".

④ Print the column count and the log in the form "Columns: 2" and "Log: SyntaxError".

JavaScript / TypeScript Editor

Run code to see output

Branching by Error Type — instanceof and name

catch receives every exception, whatever its type. But the message you want to show users depends on whether the data was malformed or a value you tried to read was missing.

instanceof (an operator that checks whether the value on the left was created from the type on the right) lets you check the type, as in error instanceof SyntaxError. error.name holds the type's name as a string, too.

const applicant = { name: "Alice", contact: null };

try {
  console.log(applicant.contact.email);
} catch (error) {
  if (error instanceof SyntaxError) {
    console.log("The data format is invalid");        // doesn't get here
  } else if (error instanceof TypeError) {
    console.log("The value you tried to read is missing");  // The value you tried to read is missing
  } else {
    console.log(`Unexpected error type: ${error.name}`);
  }

  console.log(error.name);                       // TypeError
}
Three Exceptions, One catch
Exception throwninside tryparse malformedJSONRead contactwhile it's nullAsk toFixed for200 digitsname isSyntaxErrorname isTypeErrorname isRangeErrorSay the formatis invalidSay the valueis missingLog it asunexpected
All three arrive at the same catch (error), but each has a different name. Checking the type lets you show different messages.

Exceptions that match none of the checks are all caught by the final else. The table below lists four types you'll run into often and when each one is thrown.

TypeWhen it's thrownExample code that throws it
SyntaxErrorThe string isn't in JSON formatJSON.parse("{,}")
TypeErrorYou read a property of null or undefinedapplicant.contact.email
RangeErrorYou passed a value outside the allowed range, such as a digit count(1980).toFixed(200) (digits go up to 100)
ReferenceErrorYou read a name that was never declaredconsole.log(total)

Process readings sent as strings from temperature sensors. payloads is already declared, and two of the three have problems.

① Parse each one and print it in the form "T-1: 21.5 degrees", with the temperature rounded to 1 decimal place.

② If the format is broken, print "The received data format is invalid".

③ If there's no temperature, print "No temperature reading".

④ Otherwise, print "Unexpected: " followed by the error type's name.

JavaScript / TypeScript Editor

Run code to see output
QUIZ

Knowledge Check

Answer each question one by one.

Q1What does try { console.log(1); } finally { console.log(2); } print?

Q2If a function's catch only logs and doesn't include throw error;, what return value does the caller get?

Q3Which operator do you use to check the type of the error that catch received?