What an expected failure looks like

Some operations can fail even when your program is written correctly. A person might paste invalid JSON, a requested file might be unavailable, or a network request might not complete. These are different from bugs such as misspelled variable names: they are possibilities your program can anticipate and respond to.

In JavaScript, an exception interrupts the normal flow of the current operation. A try block marks code that might throw an exception. If it does, execution moves to the matching catch block. A finally block, when present, runs afterward whether the try block succeeded or failed. You can try the synchronous examples below in a modern browser’s developer console; they need no packages or project setup.

Catch a failure you know how to handle

JSON.parse converts JSON text into a JavaScript value, but malformed text makes it throw a SyntaxError. If the text came from a user, showing a useful message is better than letting that error interrupt the rest of the interaction. For example: function readSettings(text) { try { return JSON.parse(text); } catch (error) { if (error instanceof SyntaxError) { return { error: "Settings must be valid JSON." }; } throw error; } }

Calling readSettings('{"theme":"dark"}') returns an object with a theme property. Calling readSettings('{theme:dark}') returns the error object instead, because JSON requires quoted property names and string values. The catch block handles only the failure it expects. If some other error reaches it, throw error passes that error along rather than disguising a possible bug as bad input.

Think about what the caller needs before choosing a fallback. Returning an error object is one option, but it requires callers to check for the error property before using the settings. Another option is to show a message near the input and leave the previous settings unchanged. Avoid returning an empty object merely to make the exception disappear: the program may then continue with settings that the user never chose.

Use finally for cleanup, not error handling

A finally block is useful for work that must happen after an attempt, regardless of the outcome. In a user interface, you might clear a busy indicator after an operation. This example uses a small function to stand in for that indicator: let busy = false; function loadSettings(text) { busy = true; try { return JSON.parse(text); } catch (error) { if (error instanceof SyntaxError) { return null; } throw error; } finally { busy = false; } }

After loadSettings runs, busy is false whether parsing succeeds, returns null for invalid JSON, or throws a different error. Finally does not replace catch: it performs cleanup, while catch decides how to respond to a failure. It also runs before a return from try or catch completes. Avoid returning a value or throwing a new error from finally unless you deliberately want to override the earlier result or error; that behavior can make failures difficult to diagnose.

Keep the try block focused

Put only the operations relevant to a particular failure inside try. A large try block may catch an unrelated programming mistake and make it look like the expected failure. For example, if parsing input is the risky step, parse it first and handle a SyntaxError there. Then perform other calculations outside that catch block, where their errors remain visible.

A catch block should also provide a clear next step. Depending on the task, it might ask for corrected input, use a documented default, or pass the error to a caller that can handle it. Logging an error and then continuing as if the operation succeeded often causes a more confusing problem later. Do not assume that every exception can be safely recovered from at the point where it occurs.

Remember that asynchronous work has a boundary

A synchronous try block catches exceptions thrown while that block is executing. It does not automatically catch a failure that happens later in a callback. With JavaScript’s async and await syntax, you can use try and catch around an awaited promise. Async and await require an environment that supports ECMAScript 2017 or later; modern browser developer consoles generally do.

For example, inside an async function you could write: try { const response = await fetch(address); } catch (error) { console.error("Request could not complete", error); }. A rejected fetch promise reaches catch. An HTTP error response, however, does not by itself make fetch throw; check response.ok if your program needs to treat that response as a failure. The guiding question is the same for synchronous and asynchronous code: which failures can this block meaningfully handle, and which should it leave for its caller?

Back to category ↗