# JavaScript try / catch / finally: Handling Errors Safely

Source: https://devdocs10.com/articles/javascript/js-try-catch-finally  
By Murat Bo · Published 2026-07-02 · DevDocs10 (https://devdocs10.com)

JavaScript’s `try` / `catch` / `finally` statement lets you handle runtime errors without crashing your program. It is the standard way to protect risky code, report problems clearly, and run cleanup logic no matter what happens.

> **Quick answer:** Put code that might fail inside `try`, handle the error in `catch`, and place cleanup code in `finally`. If an error is thrown in `try`, execution jumps to `catch`; `finally` runs after that either way.

> **Difficulty:** Beginner
>
> **You'll understand this better if you know:** basic JavaScript statements, functions, and how runtime errors differ from syntax errors.

## 1. What Is try / catch / finally?

`try` / `catch` / `finally` is JavaScript’s built-in error-handling structure. It gives you a controlled way to react when code throws an exception instead of letting the error stop the program immediately.

- `try` contains code that might throw an error.
- `catch` runs only if an error is thrown in `try`.
- `finally` runs after `try` and `catch`, whether an error happened or not.
- It is used for runtime errors, not syntax errors.

This matters because many JavaScript failures are unavoidable at runtime: bad JSON, missing data, invalid user input, network issues, or a function that throws on purpose.

## 2. Why try / catch / finally Matters

Without error handling, a thrown exception can stop the current script, prevent later code from running, and leave resources in an unfinished state. With `try` / `catch` / `finally`, you can keep the app responsive and recover gracefully.

Use it when you expect a specific operation may fail and you want a fallback path. Do not use it as a substitute for normal validation. If you can detect invalid input before calling risky code, do that first.

Typical reasons to use it include parsing data, calling APIs, working with code that may throw, and cleaning up temporary state after a failure.

## 3. Basic Syntax

The structure is simple: start a guarded block with `try`, handle failures in `catch`, and optionally add `finally` for cleanup.

### Minimal pattern

The example below shows the general shape of the statement.

```javascript
try {
  // Code that may throw
  dangerousOperation();
} catch (error) {
  // Handle the error
  console.error(error);
} finally {
  // Cleanup code that always runs
  cleanup();
}
```

Output (tested with Node 24.21.0):

```text
ReferenceError: dangerousOperation is not defined
Uncaught ReferenceError: cleanup is not defined
```

Here, `catch` receives the thrown value in `error`. The `finally` block is optional, but it is useful when you need to close files, clear timers, reset UI state, or release locks.

### What each part does

`try` starts the protected region. If everything inside succeeds, JavaScript skips `catch` and continues after the whole statement. If something throws, execution jumps into `catch`. Then `finally` runs before control leaves the statement.

## 4. Step-by-Step Examples

### Example 1: Handling invalid JSON

Parsing JSON is a common place where errors appear. If the string is not valid JSON, `JSON.parse()` throws a `SyntaxError`.

```javascript
const input = '{"name": "Ava"}';

try {
  const user = JSON.parse(input);
  console.log(user.name);
} catch (error) {
  console.error('Invalid JSON:', error.message);
}
```

Output (tested with Node 24.21.0):

```text
Ava
```

This pattern is useful when reading stored data, API responses, or user-provided text that should be structured JSON.

### Example 2: Falling back to a default value

You can use `catch` to replace a failed operation with a safe default.

```javascript
function parsePageSize(value) {
  try {
    return Number.parseInt(value, 10);
  } catch {
    return 20;
  }
}
```

In this example, the function returns a fallback value if parsing fails. Notice that a `catch` block can omit the error parameter when you do not need it.

### Example 3: Running cleanup with finally

`finally` is useful when cleanup must happen regardless of success or failure.

```javascript
let connectionOpen = false;

try {
  connectionOpen = true;
  console.log('Working with the resource...');
  throw new Error('Something went wrong');
} catch (error) {
  console.error(error.message);
} finally {
  connectionOpen = false;
  console.log('Resource closed');
}
```

Output (tested with Node 24.21.0):

```text
Working with the resource...
Something went wrong
Resource closed
```

This is the pattern to use when a resource must be cleaned up even if an error happens midway.

### Example 4: Re-throwing after adding context

Sometimes you want to add context to an error and then pass it upward again.

```javascript
function loadUserProfile(json) {
  try {
    return JSON.parse(json);
  } catch (error) {
    throw new Error(`Could not load profile: ${error.message}`);
  }
}
```

This keeps the original failure visible while making the error easier to understand at a higher level of the app.

## 5. Practical Use Cases

- Parsing JSON from an API response or local storage.
- Handling user input that may not meet the expected format.
- Wrapping code that throws custom errors from your own functions.
- Cleaning up resources in `finally`, such as temporary state or locks.
- Adding context to low-level errors before passing them to a caller.

In real projects, `try` / `catch` / `finally` is often used at boundaries: inside data-loading functions, command handlers, and request processors.

## 6. Common Mistakes

### Mistake 1: Expecting try / catch to handle syntax errors in the file

`try` / `catch` only handles errors that happen while the code runs. It cannot rescue a script that fails to parse before execution starts.

**Problem:** This file contains invalid syntax, so JavaScript cannot even start running the `try` block.

```javascript
try {
  const message = 'hello'
  // missing closing brace below causes a parse failure
} catch (error) {
  console.error(error);
}
```

**Fix:** Make sure the file is valid JavaScript first. Use `try` / `catch` for runtime failures, not syntax mistakes.

```javascript
try {
  const message = 'hello';
  console.log(message);
} catch (error) {
  console.error(error);
}
```

Output (tested with Node 24.21.0):

```text
hello
```

The corrected version works because the code can be parsed and the handler is available if a runtime error occurs.

### Mistake 2: Forgetting that async errors need await inside try

When a promise rejects, the rejection is only caught if the awaited expression is inside the `try` block.

**Problem:** In this code, the promise rejection happens after the `try` block finishes, so the `catch` block never sees it.

```javascript
try {
  const response = fetch('/api/data');
  console.log(response);
} catch (error) {
  console.error('Request failed');
}
```

**Fix:** Use `await` inside the `try` block so a rejected promise becomes a caught exception in the async function.

```javascript
async function loadData() {
  try {
    const response = await fetch('/api/data');
    return await response.json();
  } catch (error) {
    console.error('Request failed', error);
  }
}
```

The fixed version works because the rejection is observed at the `await` point.

### Mistake 3: Assuming finally replaces catch

`finally` is for cleanup, not error handling. It does not receive the error and should not be used to decide whether the operation failed.

**Problem:** This code tries to recover in `finally`, but the error is still unhandled because no `catch` block exists.

```javascript
try {
  throw new Error('Network down');
} finally {
  console.log('Trying to recover');
}
```

Output (tested with Node 24.21.0):

```text
Trying to recover
Uncaught Error: Network down
```

**Fix:** Add a `catch` block for recovery logic, and keep `finally` for cleanup only.

```javascript
try {
  throw new Error('Network down');
} catch (error) {
  console.error('Recovering from error:', error.message);
} finally {
  console.log('Cleanup complete');
}
```

Output (tested with Node 24.21.0):

```text
Recovering from error: Network down
Cleanup complete
```

The corrected version works because error handling and cleanup each have the right job.

## 7. Best Practices

### Practice 1: Catch only where you can actually recover

If you cannot do anything useful with an error, let it propagate to a higher-level handler. This keeps your code honest and avoids hiding bugs.

```javascript
function getParsedUser(text) {
  try {
    return JSON.parse(text);
  } catch (error) {
    throw error;
  }
}
```

This approach is better than swallowing the error because callers still know something went wrong.

### Practice 2: Use finally for cleanup, not for branching logic

`finally` should contain code that must always run. Keep decision-making in `try` or `catch`.

```javascript
let locked = false;

try {
  locked = true;
  // work
} finally {
  locked = false;
}
```

This keeps the cleanup reliable and easy to understand.

### Practice 3: Keep the protected block as small as possible

Only place code that can reasonably fail inside `try`. Smaller blocks make it easier to identify where the error came from.

```javascript
try {
  const data = JSON.parse(input);
  return data.profile.name;
} catch (error) {
  console.error('Could not read profile name', error);
  return 'Anonymous';
}
```

Output (tested with Node 24.21.0):

```text
Could not read profile name ReferenceError: input is not defined
```

Smaller `try` blocks make debugging faster and reduce the chance of hiding unrelated failures.

## 8. Limitations and Edge Cases

- `try` / `catch` does not catch syntax errors in the file itself.
- It only handles errors thrown in the same synchronous call stack unless you `await` a promise.
- `finally` still runs if `return` or `throw` happens inside `try` or `catch`.
- If `finally` throws its own error, that new error can replace the earlier one.
- Be careful not to silently ignore errors, especially in data processing or authentication code.

> A common “not working” complaint is that a rejected promise seems to bypass `catch`. In practice, the fix is usually to `await` the promise inside an `async` function.

## 9. Practical Mini Project

Let’s build a small validator that reads a JSON string, extracts a user name, and always reports cleanup work.

```javascript
function readUserName(jsonText) {
  let status = 'starting';

  try {
    status = 'parsing';
    const user = JSON.parse(jsonText);

    if (!user.name) {
      throw new Error('User name is missing');
    }

    status = 'done';
    return user.name;
  } catch (error) {
    status = 'failed';
    console.error('Could not read user:', error.message);
    return null;
  } finally {
    console.log('Status:', status);
  }
}

console.log(readUserName('{"name":"Mia"}'));
console.log(readUserName('{"name":""}'));
```

Output (tested with Node 24.21.0):

```text
Status: done
Mia
Could not read user: User name is missing
Status: failed
null
```

This mini project shows all three parts working together: parsing in `try`, handling failures in `catch`, and logging cleanup in `finally`.

## 10. Key Points

- `try` contains code that might throw at runtime.
- `catch` handles the error only when one is thrown.
- `finally` always runs for cleanup tasks.
- Use `await` inside `try` when handling promise rejections.
- Do not use `try` / `catch` as a replacement for input validation.

## 11. Practice Exercise

- Write a function that accepts a string and tries to parse it as JSON.
- If parsing fails, return `null` and log a helpful message.
- Always print a cleanup message in `finally`.

**Expected output:** The function should return the parsed object for valid JSON, return `null` for invalid JSON, and always log that cleanup ran.

**Hint:** Use `JSON.parse()` inside `try`, handle the error in `catch`, and place the cleanup log in `finally`.

```javascript
function safeParseJson(text) {
  try {
    return JSON.parse(text);
  } catch (error) {
    console.error('Invalid JSON:', error.message);
    return null;
  } finally {
    console.log('Cleanup finished');
  }
}

console.log(safeParseJson('{"ok":true}'));
console.log(safeParseJson('not valid json'));
```

Output (tested with Node 24.21.0):

```text
Cleanup finished
{ ok: true }
Invalid JSON: Unexpected token 'o', "not valid json" is not valid JSON
Cleanup finished
null
```

## 12. Final Summary

`try` / `catch` / `finally` is the core JavaScript pattern for handling runtime errors safely. It lets you separate risky code, recovery logic, and cleanup so your programs fail in a controlled way instead of breaking unexpectedly.

Use `try` for operations that might throw, `catch` to respond to the failure, and `finally` for work that must always happen. Once you are comfortable with this structure, the next useful topic is JavaScript `throw` and custom errors, which let you create clearer failures for your own functions.
