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