Skip to content

Commit 1bca62f

Browse files
committed
feat: add error handling exercises with try/catch implementation
- Introduced new exercises focused on throwing and catching errors, including practical examples in TypeScript. - Enhanced README files to explain error handling concepts and provide clear instructions for learners. - Created corresponding solution and test files to validate the implementation of error handling logic. - Updated the FINISHED.mdx to summarize the new concepts learned in error handling.
1 parent 64046c5 commit 1bca62f

28 files changed

Lines changed: 4287 additions & 0 deletions
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Throwing and Catching Errors
2+
3+
👨‍💼 We need to convert user input into a number. If the input is invalid, we
4+
should throw an error and handle it gracefully so the program can keep running.
5+
6+
Sometimes the best control flow is to stop execution and report a problem:
7+
8+
```ts
9+
function riskyWork() {
10+
throw new Error('Something went wrong')
11+
}
12+
13+
try {
14+
riskyWork()
15+
} catch (error) {
16+
console.error('Caught an error:', error)
17+
}
18+
```
19+
20+
🐨 Open <InlineFile file="index.ts" /> and:
21+
22+
1. Throw an error in `parseNumber` when the input isn't a valid number
23+
2. Wrap the call to `parseNumber(rawInput)` in a `try`/`catch`
24+
3. Set `resultMessage` to show either the parsed value or the error message
25+
26+
<callout-info>
27+
In a `catch` block, the `error` value is `unknown`. Use
28+
`error instanceof Error` before reading `error.message`.
29+
</callout-info>
30+
31+
📜 [MDN - throw](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/throw)
32+
📜 [MDN - try...catch](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch)
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Throwing and Catching Errors
2+
// Errors interrupt normal control flow, but try/catch lets you recover
3+
4+
const rawInput = 'not-a-number'
5+
6+
function parseNumber(value: string) {
7+
const parsed = Number(value)
8+
9+
if (Number.isNaN(parsed)) {
10+
// 🐨 Throw a new Error with message `Invalid number: ${value}`
11+
// 💰 throw new Error(`Invalid number: ${value}`)
12+
}
13+
14+
return parsed
15+
}
16+
17+
let resultMessage = ''
18+
19+
// 🐨 Use a try/catch block to call parseNumber(rawInput)
20+
// - If it succeeds, set resultMessage to `Parsed value: ${parsed}`
21+
// - If it throws, set resultMessage to `Error: ${message}`
22+
// 💰 In the catch, check `error instanceof Error` before using `error.message`
23+
24+
// console.log(resultMessage)
25+
26+
// 🐨 Export parseNumber and resultMessage so we can verify your work
27+
// export { parseNumber, resultMessage }
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"name": "exercises_04.control-flow_06.problem.throwing-and-catching",
3+
"type": "module",
4+
"scripts": {
5+
"start": "npx @kentcdodds/log-module ./index.ts",
6+
"start:watch": "npx @kentcdodds/log-module --watch ./index.ts",
7+
"test": "node --test index.test.ts",
8+
"test:watch": "node --watch --test index.test.ts"
9+
}
10+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Throwing and Catching Errors
2+
3+
👨‍💼 Nice work! You made invalid input throw an error and used `try`/`catch` to
4+
recover with a helpful message.
5+
6+
When you throw, you immediately exit the current flow. `catch` gives you a
7+
safe place to decide what happens next.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import assert from 'node:assert/strict'
2+
import { test } from 'node:test'
3+
import * as solution from './index.ts'
4+
5+
await test('parseNumber is exported', () => {
6+
assert.ok('parseNumber' in solution, '🚨 Make sure you export "parseNumber"')
7+
})
8+
9+
await test('parseNumber returns a number for valid input', () => {
10+
assert.strictEqual(
11+
solution.parseNumber('42'),
12+
42,
13+
'🚨 parseNumber should return 42 when given "42"',
14+
)
15+
})
16+
17+
await test('parseNumber throws for invalid input', () => {
18+
assert.throws(
19+
() => solution.parseNumber('not-a-number'),
20+
{
21+
message: 'Invalid number: not-a-number',
22+
},
23+
'🚨 parseNumber should throw an Error for invalid input',
24+
)
25+
})
26+
27+
await test('resultMessage is exported', () => {
28+
assert.ok(
29+
'resultMessage' in solution,
30+
'🚨 Make sure you export "resultMessage"',
31+
)
32+
})
33+
34+
await test('resultMessage should show the error message', () => {
35+
assert.strictEqual(
36+
solution.resultMessage,
37+
'Error: Invalid number: not-a-number',
38+
'🚨 resultMessage should include the thrown error message',
39+
)
40+
})
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Throwing and Catching Errors
2+
// Errors interrupt normal control flow, but try/catch lets you recover
3+
4+
const rawInput = 'not-a-number'
5+
6+
function parseNumber(value: string) {
7+
const parsed = Number(value)
8+
9+
if (Number.isNaN(parsed)) {
10+
throw new Error(`Invalid number: ${value}`)
11+
}
12+
13+
return parsed
14+
}
15+
16+
let resultMessage = ''
17+
18+
try {
19+
const parsed = parseNumber(rawInput)
20+
resultMessage = `Parsed value: ${parsed}`
21+
} catch (error) {
22+
if (error instanceof Error) {
23+
resultMessage = `Error: ${error.message}`
24+
} else {
25+
resultMessage = 'Error: Unknown error'
26+
}
27+
}
28+
29+
console.log(resultMessage)
30+
31+
export { parseNumber, resultMessage }
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"name": "exercises_04.control-flow_06.solution.throwing-and-catching",
3+
"type": "module",
4+
"scripts": {
5+
"start": "npx @kentcdodds/log-module ./index.ts",
6+
"start:watch": "npx @kentcdodds/log-module --watch ./index.ts",
7+
"test": "node --test index.test.ts",
8+
"test:watch": "node --watch --test index.test.ts"
9+
}
10+
}

exercises/04.control-flow/FINISHED.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@ You learned:
99
- 🔁 **`for` loops** for counted repetition
1010
- 🔄 **`while` loops** for conditional repetition
1111
-**Ternary operator** for concise value selection
12+
- 🧯 **`try`/`catch`** for handling errors
1213

1314
Next up: Functions—packaging up reusable logic with type-safe contracts!

exercises/04.control-flow/README.mdx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,19 @@ const status = age >= 18 ? 'adult' : 'minor'
115115

116116
It has three parts: condition, value if true, value if false.
117117

118+
## Error Handling
119+
120+
Sometimes code needs to stop early when something goes wrong. You can **throw**
121+
an error to interrupt normal flow, and **catch** it to recover:
122+
123+
```ts
124+
try {
125+
riskyWork()
126+
} catch (error) {
127+
console.error(error)
128+
}
129+
```
130+
118131
<callout-info>
119132
TypeScript's type narrowing works inside conditionals. When you check a type,
120133
TypeScript knows the more specific type inside that block.

instructor/01-workshop-overview.md

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
# Epic Workshop Overview
2+
3+
## What is an Epic Workshop?
4+
5+
An Epic Workshop is a hands-on, interactive learning experience where students learn by writing code. The Epic Workshop App provides the infrastructure to run these workshops locally, presenting exercises with problems to solve and solutions to compare against.
6+
7+
## Core Terminology
8+
9+
Understanding these terms is essential:
10+
11+
### Workshop
12+
The entire learning experience. A workshop has a title, subtitle, and contains multiple exercises. Example: "React Fundamentals" or "Mocking Techniques in Vitest"
13+
14+
### Exercise
15+
A major learning topic within a workshop. Each exercise has:
16+
- An introduction (`README.mdx`) explaining the concept
17+
- Multiple steps (problem/solution pairs)
18+
- A summary (`FINISHED.mdx`)
19+
20+
Example exercises: "Hello World in JS", "Functions", "Form Validation"
21+
22+
### Step
23+
A focused, atomic task within an exercise. Each step has:
24+
- A **Problem**: The starting state where students implement something
25+
- A **Solution**: The expected final state students should arrive at
26+
27+
### Problem
28+
The initial code state. Contains:
29+
- Starter code with TODO comments and emoji markers
30+
- A `README.mdx` with instructions
31+
- All necessary configuration files
32+
33+
### Solution
34+
The completed code state. Contains:
35+
- Fully implemented code
36+
- A `README.mdx` explaining the solution
37+
- Often includes tests to verify the solution
38+
39+
### Playground
40+
A sandbox area where learners can experiment. When they click "Set to Playground" in the UI, the app copies the contents of any exercise step to the playground.
41+
42+
### Example
43+
Standalone, runnable code samples in the `examples/` directory that demonstrate concepts without being exercises.
44+
45+
## Workshop Structure Hierarchy
46+
47+
```
48+
Workshop
49+
├── exercises/
50+
│ ├── README.mdx (Workshop Intro)
51+
│ ├── 01.exercise-name/
52+
│ │ ├── README.mdx (Exercise Intro)
53+
│ │ ├── 01.problem.step-name/
54+
│ │ │ ├── README.mdx (Problem Instructions)
55+
│ │ │ └── [code files]
56+
│ │ ├── 01.solution.step-name/
57+
│ │ │ ├── README.mdx (Solution Explanation)
58+
│ │ │ └── [code files]
59+
│ │ ├── 02.problem.next-step/
60+
│ │ ├── 02.solution.next-step/
61+
│ │ └── FINISHED.mdx (Exercise Summary)
62+
│ ├── 02.next-exercise/
63+
│ └── FINISHED.mdx (Workshop Wrap-up)
64+
├── examples/
65+
├── playground/
66+
└── package.json
67+
```
68+
69+
## App Types
70+
71+
Epic Workshops support two types of apps:
72+
73+
### Simple Apps
74+
No `package.json` with a `dev` script. The workshop app serves files directly.
75+
76+
**Use for:**
77+
- HTML/CSS/JS exercises
78+
- Single-file React exercises
79+
- Quick demonstrations
80+
81+
**Files:**
82+
- `index.tsx` or `index.html` (required)
83+
- `index.css` (optional, auto-included)
84+
- `api.server.ts` (optional, for server-side logic)
85+
86+
### Project Apps
87+
Has a `package.json` with a `dev` script. Runs as a separate server.
88+
89+
**Use for:**
90+
- Full applications (Remix, Vite, etc.)
91+
- Exercises requiring build tools
92+
- Complex multi-file projects
93+
94+
**Requirements:**
95+
- `package.json` with `"scripts": { "dev": "..." }`
96+
- Dev server must use the `PORT` environment variable
97+
98+
## Learning Flow
99+
100+
The typical learner experience:
101+
102+
1. **Read the Exercise Intro** - Understand the concept being taught
103+
2. **Read the Problem Instructions** - Understand what to implement
104+
3. **Implement the Solution** - Write code in the problem directory
105+
4. **Compare with Solution** - Use the diff tab to see differences
106+
5. **Run Tests** (if available) - Verify the implementation
107+
6. **Read the Summary** - Reflect on what was learned
108+
7. **Move to Next Step** - Continue the learning journey
109+
110+
## Key Design Principles
111+
112+
### 1. Incremental Complexity
113+
Start with the simplest possible example and build complexity gradually. Each step should introduce ONE new concept.
114+
115+
### 2. Problem-Solution Pairs
116+
Every problem must have a corresponding solution. The solution should be the minimal change needed from the problem to complete the task.
117+
118+
### 3. Clear Instructions
119+
Use emoji characters to guide learners:
120+
- 👨‍💼 Peter the Product Manager - Context and requirements
121+
- 🐨 Kody the Koala - Specific instructions
122+
- 🧝‍♀️ Kellie the Co-worker - Pre-done work context
123+
- 🦺 Lily the Life Jacket - TypeScript guidance
124+
- 💰 Marty the Money Bag - Tips and hints
125+
- 📜 Dominic the Document - Documentation links
126+
- 📝 Nancy the Notepad - Note-taking prompts
127+
- 🦉 Olivia the Owl - Best practices
128+
- 💣 Barry the Bomb - Code to delete
129+
- 🚨 Alfred the Alert - Test failure help
130+
131+
### 4. Real-World Relevance
132+
Exercises should mirror actual development tasks. Use realistic examples, not contrived scenarios.
133+
134+
### 5. Self-Contained Steps
135+
Each step should be completable independently. Don't require knowledge from later steps to complete earlier ones.
136+
137+
## What Makes a Great Workshop
138+
139+
Based on analysis of successful Epic Workshops:
140+
141+
1. **Clear Learning Objectives** - Each exercise has a specific goal
142+
2. **Appropriate Scope** - 6-10 exercises, 2-6 steps per exercise
143+
3. **Consistent Difficulty Progression** - Gets harder gradually
144+
4. **Rich Context** - Explains "why" not just "what"
145+
5. **Quality Code** - Production-grade patterns and practices
146+
6. **Helpful Diffs** - Problem and solution differ meaningfully but not overwhelmingly
147+
7. **Engaging Content** - Uses emoji characters, videos, and callouts effectively

0 commit comments

Comments
 (0)