05open 25 min

Error handling: from Python exceptions to thrown errors and rejected Promises

Map Python exception instincts onto JavaScript and Node's sync and async failure shapes.

by the end of this lesson you can

  • Distinguishes validation from I/O failure clearly
  • Uses `throw` and `await` coherently
  • Keeps the failure path obvious to the next reader

Overview

Python developers already understand exceptions, but JavaScript adds a split: failures can be thrown synchronously or arrive as Promise rejections in async flows. Good Node code makes that distinction understandable instead of hand-wavy.

In Python, you often

catch exceptions with `try`/`except` and assume the failure path stays inside a relatively direct control-flow model.

In JavaScript/Node, the common pattern is

to use `try`/`catch` around `await` and stay alert to whether the failure is synchronous, asynchronous, or part of an API contract.

why this difference matters

The syntax looks familiar, but the runtime behavior is not identical. Clear Node code makes it obvious where an operation can fail and how that failure propagates.

Python

try:
    user = load_user(user_id)
except ValueError as error:
    logger.error(error)

JavaScript/Node

try {
  const user = await loadUser(userId);
} catch (error) {
  console.error(error);
}

Deeper comparison

Python version

def save_user(user):
    if not user.name:
        raise ValueError("missing name")
    return repository.save(user)

JavaScript/Node version

async function saveUser(user) {
  if (!user.name) {
    throw new Error("missing name");
  }

  return repository.save(user);
}

Reflect

What do you need to keep explicit so a JavaScript or Node failure path stays as understandable as a Python one?

what a strong answer notices

A strong answer mentions where rejections happen, how `await` changes the shape of control flow, and how validation errors differ from infrastructure failures.

Rewrite

Rewrite this Python exception flow into JavaScript or Node without hiding whether the operation is async.

Rewrite this Python

def load_profile(user_id):
    if not user_id:
        raise ValueError("missing id")
    return repository.get(user_id)

what good looks like

  • Distinguishes validation from I/O failure clearly
  • Uses `throw` and `await` coherently
  • Keeps the failure path obvious to the next reader

Practice

Design a Node service function that validates input, loads data asynchronously, and returns a useful error when either step fails.

success criteria

  • Handles validation early
  • Uses async error handling consistently
  • Avoids collapsing all failures into an indistinct catch-all

Common mistakes

  • Assuming every JavaScript failure behaves like a normal Python exception.
  • Catching too broadly and hiding useful error context.
  • Forgetting that async APIs can fail through rejection rather than direct throw.

takeaways

  • The syntax looks familiar, but the runtime behavior is not identical. Clear Node code makes it obvious where an operation can fail and how that failure propagates.
  • A strong answer mentions where rejections happen, how `await` changes the shape of control flow, and how validation errors differ from infrastructure failures.
  • Handles validation early