What you'll learn
Errors are part of an application's contract. Strong JavaScript systems distinguish expected domain failures from defects and preserve enough context to diagnose both.
By the end of this lesson, you'll be able to:
- Throw and catch at useful boundaries
- Create domain-specific errors
- Debug source-mapped production code
Core mental model
Use this decision table as a compact reference. Focus on what each tool means and when it earns its place in production code.
| Concept | What it means | Decision rule |
|---|---|---|
| Exception | Non-local transfer of failure control | Throw when the current layer cannot fulfill its contract |
| Custom error | Failure with stable type and structured context | Use when callers need different recovery behavior |
| Source map | Maps generated code back to authored source | Upload securely to diagnostics tooling |
Professional workflow
Build the behavior in small, observable steps. Each step should leave something you can inspect or test.
- Describe the failure and diagnosis boundary boundary: inputs, outputs, state, timing, and expected failures.
- Implement the smallest correct path with names that expose intent.
- Add edge cases and failure handling before introducing abstractions.
- Verify behavior with realistic data and one deliberately adversarial example.
- Refactor only after the observable behavior is protected.
Make behavior observable
Guided code lab
Preserve a cause chain
The higher layer adds domain meaning without losing the original failure.
class CourseLoadError extends Error {
constructor(courseId, options) {
super("Could not load course " + courseId, options);
this.name = "CourseLoadError";
this.courseId = courseId;
}
}
async function loadCourse(id) {
try { return await requestJson("/api/courses/" + id); }
catch (error) { throw new CourseLoadError(id, { cause: error }); }
}Recover only where recovery is possible
The UI boundary translates a typed failure into user-facing state.
try {
const course = await loadCourse("javascript");
renderCourse(course);
} catch (error) {
if (error instanceof CourseLoadError) renderRetry(error.courseId);
else throw error;
}Production practice
Contract
Document the failure and diagnosis boundary inputs, completion signal, failure channel, ordering, and cancellation behavior.
Verification
Test success, expected failure, timeout or cancellation, empty data, and out-of-order completion.
Operations
Expose duration and failure context without logging secrets or overwhelming the main thread.
Common failure mode
Independent workshop
Add diagnostic error boundaries to the API dashboard.
Your finished workshop must include:
- At least two custom error types
- Preserved error causes
- A debugging note using breakpoints and a source map
Definition of done
Recap & quick check
Key takeaways
- Throw when a contract cannot be met
- Catch where recovery exists
- Custom errors encode domain meaning
- Source maps reconnect generated and authored code
Quick check
1. What does finally guarantee?
2. Why use Error cause?
3. Where should an error be caught?
Keep the workshop. Later modules deliberately build on these decisions, so each exercise can become part of your final portfolio architecture.