Lesson 0.7.2 — Throw
0. Metadata
| Field | Value |
|---|---|
| Stage | 0 — JavaScript Language Foundation |
| Module | 0.7 — Error Handling & Code Quality |
| Lesson | 0.7.2 |
| Competency | C01.8 — Error Handling |
| Depth Target | L2–L3 |
| Prerequisites | Errors (0.7.1), Functions (0.5.1), Control Flow (0.4.x) |
| Estimated Cognitive Load | Medium |
1. Why This Exists (Vì sao cần học)
Bạn đã biết error là một object chứa thông tin. Nhưng error object chỉ ngồi im trong bộ nhớ thì vô dụng. Nó cần được kích hoạt — để dừng luồng hiện tại và báo hiệu cho phần còn lại của chương trình biết rằng "đã có chuyện không ổn".
Xét đoạn code:
function divide(a, b) {
if (b === 0) {
return null;
}
return a / b;
}
const result = divide(10, 0);
console.log(result + 5); // null + 5 = 5 ❌Vấn đề cốt lõi
Trả về null để báo lỗi là silent failure. Caller không biết đó là lỗi hay kết quả hợp lệ. null + 5 thành 5 — bug lan truyền âm thầm.
throw giải quyết điều này bằng cách:
- Dừng ngay lập tức function hiện tại.
- Truyền error object lên call stack cho đến khi gặp ai đó xử lý.
- Bắt buộc caller phải đối mặt với lỗi — không thể vô tình ignore.
Bài này dạy bạn dùng throw như một cơ chế propagation: không phải để trừng phạt, mà để thông tin lỗi di chuyển đúng hướng trong chương trình.
2. Prerequisites (Yêu cầu đầu vào)
Trước khi học bài này, bạn cần:
- Biết error là object với
message,name,stack(0.7.1). - Biết function call và return (0.5.1).
- Hiểu
ifvà control flow (0.4.2). - Biết
undefined,nulllà giá trị hợp lệ có thể bị nhầm với lỗi (0.2.3).
WARNING
Nếu bạn chưa chắc tại sao return null để báo lỗi là nguy hiểm, quay lại 0.7.1. Throw chỉ có ý nghĩa khi bạn hiểu sự khác biệt giữa "trả về giá trị đặc biệt" và "dừng execution với lỗi".
3. Learning Objectives (Mục tiêu học tập)
Sau bài này, bạn có thể:
- Giải thích
throwdừng execution và truyền value lên call stack. - Sử dụng
throwvớiErrorobject để báo lỗi rõ ràng. - Dự đoán luồng execution khi gặp
throwtrong nested function calls. - Phân biệt throw
Errorobject và throw primitive value. - Nhận diện uncaught error và hiểu hậu quả (crash script).
- Viết guard clause kết hợp
throwđể validate input sớm.
4. Mental Model (Mô hình tư duy)
Mental Model
Throw = Ném "bóng lỗi" lên trên
throw new Error("fail")
↓
Function hiện tại DỪNG NGAY LẬP TỨC
↓
Bóng lỗi bay lên function gọi nó
↓
Function gọi cũng dừng (trừ khi có try/catch — 0.7.3)
↓
Cứ thế cho đến top-level
↓
Nếu không ai bắt → Uncaught Error → Crash
Propagation Chain
inner()
↓ throw
middle()
↓ (không catch — dừng)
outer()
↓ (không catch — dừng)
Top-level → CrashQuy tắc vàng:
throwlà một statement, không phải expression. Không thể viếtconst x = throw ....throwcó thể ném bất kỳ value nào, nhưng convention là némErrorobject.- Khi
throwxảy ra, mọi code sau nó trong cùng block/function không chạy. - Nếu không có
try/catchtrên đường đi, error trở thành uncaught và script dừng (trong synchronous code).
5. Core Concepts (Các khái niệm cốt lõi)
Essential (Bắt buộc)
| Concept | Ý nghĩa |
|---|---|
throw statement | throw expression; — dừng execution hiện tại và truyền value lên call stack. |
| Propagation | Error đi ngược lại call stack cho đến khi được catch hoặc thoát ra ngoài. |
| Uncaught error | Error không được xử lý. Trong browser: console error + script dừng. Trong Node.js: process exit với code ≠ 0. |
| Guard clause + throw | if (!valid) throw new Error(...) — dừng sớm khi input không hợp lệ. |
| Throw Error object | throw new TypeError("...") — mang đầy đủ message, stack, name. |
Supporting (Hỗ trợ)
| Concept | Ý nghĩa |
|---|---|
| Re-throw | catch một lỗi, xử lý một phần, rồi throw lại. Preview cho 0.7.3. |
| Throw in expression context workaround | throw là statement nên không dùng trong expression trực tiếp. Dùng IIFE hoặc tách biệt. |
Awareness (Biết tồn tại)
| Concept | Lý do chưa đào sâu |
|---|---|
| Custom Error class | class ValidationError extends Error. Thuộc Stage 2 (Class/Object Model). |
| Error cause chaining | new Error("...", { cause: err }). ES2022. Thuộc Stage 9/13. |
Out of Scope (Không thuộc bài này)
try/catch/finallymechanism. Thuộc 0.7.3.- Async throw / Promise rejection. Thuộc Stage 3.
- Error Boundary trong React. Thuộc Stage 8.
6. Worked Example (Ví dụ phân tích từng bước)
Bài toán: Theo dõi propagation của error qua 3 tầng function.
function parseId(value) {
if (typeof value !== "number") {
throw new TypeError("ID must be a number");
}
if (value <= 0) {
throw new RangeError("ID must be positive");
}
return value;
}
function fetchUser(rawId) {
const id = parseId(rawId);
return { id, name: "User " + id };
}
function renderUser(rawId) {
const user = fetchUser(rawId);
return `<h1>${user.name}</h1>`;
}
console.log(renderUser("abc"));Output (V8):
TypeError: ID must be a number
at parseId (app.js:3:11)
at fetchUser (app.js:10:14)
at renderUser (app.js:15:16)
at Object.<anonymous> (app.js:18:13)Walkthrough
Step 1 — Throw tại parseIdrawId là "abc", typeof là "string". Điều kiện !== "number" đúng → throw new TypeError(...). Dòng return value; không bao giờ chạy.
Step 2 — Propagation qua fetchUserfetchUser gọi parseId("abc"). Vì parseId throw, fetchUser cũng dừng ngay lập tức. Dòng return { id, name: ... } không chạy. Error object "bay qua" fetchUser.
Step 3 — Propagation qua renderUserrenderUser gọi fetchUser("abc"). fetchUser dừng do throw → renderUser cũng dừng. Dòng return `<h1>${user.name}</h1>` không chạy.
Step 4 — Top-levelrenderUser("abc") tại top-level throw. Không ai catch → uncaught error. Console in stack trace. Script dừng. console.log sau đó (nếu có) không chạy.
Step 5 — Đọc stack trace
- Top frame:
parseId— nơi throw. - Frame 2:
fetchUser— gọiparseId. - Frame 3:
renderUser— gọifetchUser. - Frame 4: top-level — gọi
renderUser.
Key Insight: throw là một cơ chế ngắt mạch. Nó không chỉ báo lỗi — nó ngăn chặn việc tiếp tục xử lý dữ liệu không hợp lệ qua nhiều tầng function.
7. Prediction Exercise (Bài tập dự đoán)
Đừng chạy code. Đọc và dự đoán: (1) Có lỗi không? (2) Nếu có, message và stack trace sẽ như thế nào? (3) Dòng nào không chạy?
Câu 1
function check(value) {
if (!value) {
throw new Error("Falsy value");
}
return value;
}
check(0);Câu 2
function a() { throw new Error("from a"); }
function b() { a(); return "b done"; }
function c() { b(); return "c done"; }
console.log(c());Câu 3
function validate(name) {
if (typeof name !== "string") throw "Name must be string";
if (name.length === 0) throw new Error("Name empty");
return name.toUpperCase();
}
validate("");Câu 4
function step1() { return 1; }
function step2() { throw new Error("fail"); }
function step3() { return step1() + step2(); }
console.log(step3());Câu 5
function divide(a, b) {
if (b === 0) throw new RangeError("Division by zero");
return a / b;
}
function calculate() {
const x = divide(10, 0);
console.log("Result:", x);
return x * 2;
}
calculate();Câu 6 (Transfer — Throw vs Return)
function validate(x) {
if (x < 0) throw new Error("negative");
return x * 2;
}
function pipeline(x) {
const a = validate(x);
const b = a + 10;
return b;
}
console.log(pipeline(5));
console.log(pipeline(-1));Câu hỏi:
console.log(pipeline(5))in ra gì?- Khi chạy
pipeline(-1), những dòng nào trongpipelinekhông chạy?validate(-1)có return giá trị không? - Nếu sửa
throw new Error("negative")thànhreturn null,pipeline(-1)sẽ ra kết quả gì? Điều này tốt hay xấu?
[Đáp án & Giải thích]
Câu 1: Error: Falsy value — 0 là falsy.
- Giải thích:
!0làtruenên throw.return valuekhông chạy. Stack trace:check→ top-level.
Câu 2: Error: from a — Propagation qua 3 tầng.
- Giải thích:
a()throw.b()dừng,"b done"không return.c()dừng,"c done"không return.console.log(c())không chạy. Stack:a→b→c→ top-level.
Câu 3: Error: Name empty — Throw primitive không xảy ra.
- Giải thích:
validate("")→typeof ""là"string"nên qua check đầu.name.length === 0đúng →throw new Error("Name empty"). Lưu ý: nếu truyền123, sẽ throw primitive"Name must be string"(string, không phải Error object).
Câu 4: Error: fail — step1() chạy xong rồi step2() throw.
- Giải thích:
step3()gọistep1()(trả về 1), rồi gọistep2()(throw).step3()dừng.1 + step2()không evaluate kết quả.console.logkhông chạy.
Câu 5: RangeError: Division by zero — Dừng sớm, console.log không chạy.
- Giải thích:
divide(10, 0)throw.calculate()dừng tạiconst x = ....console.log("Result:", x)không chạy.return x * 2không chạy. Stack:divide→calculate→ top-level.
Câu 6:
- Đáp án:
Câu 1: 20
- Giải thích:
validate(5)return10.pipeline:a = 10,b = 20, return20.
Câu 2: const b = a + 10 và return b không chạy. validate(-1) không return gì cả.
- Giải thích:
validate(-1)throw ngay tạiif (x < 0). Dòngreturn x * 2không chạy.pipelinedừng tạiconst a = validate(x). Các dòng sau đó trongpipelinekhông chạy.throwkhông tạo giá trị return — nó ngắt luồng hoàn toàn.
Câu 3: pipeline(-1) trả về 10 (vì null + 10 = 10). Điều này xấu.
- Giải thích:
return nullche giấu lỗi. Caller nhậnnullnhưngpipelinevẫn chạy tiếp, tạo ra kết quả sai (10) thay vì báo lỗi. Đây chính là silent failure màthrowđược thiết kế để ngăn chặn.
8. Implementation Lab (Bài lab thực hành)
Level 1 — Guided (Có hướng dẫn)
Viết function requireNumber throw TypeError nếu input không phải number.
function requireNumber(value) {
if (___________) {
throw new TypeError(___________);
}
return value;
}
// Test:
console.log(requireNumber(42)); // 42
// console.log(requireNumber("42")); // TypeErrorGợi ý
typeof value !== "number". Message: "Expected number, got " + typeof value.
[Đáp án tham khảo]
function requireNumber(value) {
if (typeof value !== "number") {
throw new TypeError("Expected number, got " + typeof value);
}
return value;
}
console.log(requireNumber(42)); // 42
// requireNumber("42");
// TypeError: Expected number, got stringGiải thích:
- Guard clause kiểm tra kiểu sớm.
throwngăn function tiếp tục với dữ liệu không hợp lệ.- Message rõ ràng giúp debug nhanh.
Level 2 — Partial Scaffold (Khung sẵn)
Hoàn thành function parseConfig để throw đúng loại lỗi.
function parseConfig(input) {
if (input === null || typeof input !== "object") {
throw new _______("Config must be an object");
}
if (!input.host) {
throw new _______("Config must have 'host'");
}
if (typeof input.port !== "number") {
throw new _______("Config.port must be a number");
}
if (input.port < 1 || input.port > 65535) {
throw new _______("Config.port out of range");
}
return input;
}
// Test:
parseConfig({ host: "a.com", port: 80 }); // ✅
// parseConfig(null); // TypeError
// parseConfig({ port: 80 }); // Error
// parseConfig({ host: "a.com", port: "80" }); // TypeError
// parseConfig({ host: "a.com", port: 99999 }); // RangeError[Đáp án tham khảo]
function parseConfig(input) {
if (input === null || typeof input !== "object") {
throw new TypeError("Config must be an object");
}
if (!input.host) {
throw new Error("Config must have 'host'");
}
if (typeof input.port !== "number") {
throw new TypeError("Config.port must be a number");
}
if (input.port < 1 || input.port > 65535) {
throw new RangeError("Config.port out of range");
}
return input;
}Giải thích:
- Sai kiểu dữ liệu →
TypeError. - Thiếu property bắt buộc →
Errorgeneric (hoặcTypeErrortùy convention). - Giá trị ngoài phạm vi →
RangeError.
Level 3 — Independent (Tự viết)
Viết các function sau:
// 1. requireNonEmptyString(str) → Nếu không phải string, throw TypeError.
// Nếu string rỗng, throw Error với message "String cannot be empty".
// 2. findById(items, id) → items là array. Nếu items không phải array, throw TypeError.
// Nếu không tìm thấy item với id khớp, throw Error với message "Item not found".
// Nếu tìm thấy, trả về item.
// 3. calculateArea(width, height) → Nếu width hoặc height <= 0, throw RangeError.
// Nếu không phải number, throw TypeError. Trả về width * height.
// Sử dụng:
console.log(requireNonEmptyString("hello")); // "hello"
// console.log(requireNonEmptyString("")); // Error
// console.log(requireNonEmptyString(123)); // TypeError
const items = [{ id: 1, name: "A" }, { id: 2, name: "B" }];
console.log(findById(items, 2)); // { id: 2, name: "B" }
// console.log(findById(items, 99)); // Error
// console.log(findById(null, 1)); // TypeError
console.log(calculateArea(5, 3)); // 15
// console.log(calculateArea(-1, 3)); // RangeError
// console.log(calculateArea("5", 3)); // TypeError[Đáp án tham khảo]
function requireNonEmptyString(str) {
if (typeof str !== "string") {
throw new TypeError("Expected string");
}
if (str.length === 0) {
throw new Error("String cannot be empty");
}
return str;
}
function findById(items, id) {
if (!Array.isArray(items)) {
throw new TypeError("Items must be an array");
}
const found = items.find(item => item.id === id);
if (!found) {
throw new Error("Item not found");
}
return found;
}
function calculateArea(width, height) {
if (typeof width !== "number" || typeof height !== "number") {
throw new TypeError("Width and height must be numbers");
}
if (width <= 0 || height <= 0) {
throw new RangeError("Dimensions must be positive");
}
return width * height;
}Giải thích:
requireNonEmptyString: Guard clause kiểm tra kiểu trước, rồi kiểm tra giá trị.findById:Array.isArraylà cách đáng tin cậy nhất để check array.findtrả vềundefinednếu không có — ta throw để báo lỗi rõ ràng thay vì trả vềundefinedgây nhầm lẫn.calculateArea: Kiểm tra kiểu trước, phạm vi sau. Thứ tự quan trọng: nếu check<= 0trước,"5" <= 0vẫn đúng nhưng đó là coercion bug.
9. Edge Cases (Các trường hợp ngoại lệ)
Throw là statement, không phải expression
const result = condition || throw new Error("fail");
// SyntaxError: Unexpected token 'throw'Tại sao: throw là statement. Không thể dùng trong expression context như ||, ??, ternary.
Cách nhận biết: SyntaxError ngay khi parse.
Cách xử lý: Dùng if statement riêng biệt:
if (!condition) throw new Error("fail");
const result = condition;Hoặc IIFE (không khuyến khích ở Stage 0):
const result = condition || (() => { throw new Error("fail"); })();Lưu ý: Không thể dùng
throwtrong expression context (như||,??, ternary). Giải pháp duy nhất ở Stage 0 là tách thành statement riêng. Các pattern nâng cao như IIFE sẽ được học ở Stage 1.
Throw primitive mất stack trace
throw "Something went wrong";Tại sao: String không có stack property. Khi log, bạn chỉ thấy message mà không biết lỗi từ đâu ra.
Cách nhận biết: Console chỉ in string, không có at function (file:line).
Cách xử lý: Luôn throw new Error("...").
Uncaught error dừng script
console.log("A");
throw new Error("fail");
console.log("B");Tại sao: Không có try/catch. Error thoát ra top-level. Engine dừng execution.
Cách nhận biết: "B" không in ra. Console có stack trace đỏ.
Cách xử lý: Trong production, uncaught error là crash. Cần try/catch (0.7.3) hoặc global handler (Stage 9).
Throw trong return expression (không được)
function fn() {
return throw new Error("x");
}Tại sao: throw là statement, không phải expression. Không thể là toán hạng của return.
Cách nhận biết: SyntaxError.
Cách xử lý: Tách ra:
function fn() {
throw new Error("x");
}10. Debug Lab (Bài lab gỡ lỗi)
Symptom (Triệu chứng): Function trả về undefined thay vì throw khi input sai.
function getUser(id) {
if (typeof id !== "number") {
new Error("ID must be a number");
}
if (id <= 0) {
throw "ID must be positive";
}
return { id };
}
const user = getUser("abc");
console.log(user.id);
// TypeError: Cannot read properties of undefined (reading 'id')Reproduction (Tái hiện lỗi): Chạy code với getUser("abc").
Evidence (Bằng chứng):
userlàundefined. Nghĩa làgetUserđã chạy đến cuối mà không return gì (implicitundefined).- Không có error nào được throw cả.
- Dòng
new Error("...")tạo object nhưng không throw nó.
Hypothesis (Giả thuyết): Developer quên từ khóa throw. Họ viết new Error(...) nhưng không throw nó. Và ở check thứ hai, họ throw primitive string thay vì Error object.
Verification (Xác minh):
const err = new Error("test");
console.log(err); // Error object tồn tại, nhưng không dừng executionRoot Cause (Nguyên nhân gốc rễ):
new Error(...)chỉ tạo object.throwmới kích hoạt propagation.throw "ID must be positive"là primitive, mất stack trace.
Fix (Sửa):
function getUser(id) {
if (typeof id !== "number") {
throw new TypeError("ID must be a number");
}
if (id <= 0) {
throw new RangeError("ID must be positive");
}
return { id };
}Prevention (Phòng ngừa):
- Luôn kiểm tra:
throwđi kèmnew Error? Hay chỉ cónew Errorđơn độc? - Lint rule hoặc code review: "Naked
new Errorwithout throw". - Không throw primitive. Nếu thấy
throw "...", sửa thànhthrow new Error("...").
11. Design Exercise (Bài tập thiết kế giải pháp)
Bạn cần viết hàm findUser.
function findUser(users, id) {
const user = users.find(u => u.id === id);
return user || null;
}function findUser(users, id) {
const user = users.find(u => u.id === id);
if (!user) {
throw new Error("User not found");
}
return user;
}Câu hỏi:
- Option A có lợi gì khi "không tìm thấy" là kết quả bình thường (ví dụ: search)?
- Option B có lợi gì khi "không tìm thấy" là lỗi logic (ví dụ: lookup by ID từ database)?
- Nếu caller của Option A quên check
null, bug gì xảy ra? Nếu caller của Option B khôngtry/catch, bug gì xảy ra?
[Đáp án tham khảo]
Bạn nghĩ:
- Option A: Không làm gián đoạn luồng. Phù hợp khi "not found" là expected outcome. Caller tự quyết định xử lý.
- Option B: Bắt buộc caller đối mặt với lỗi. Không thể vô tình ignore. Phù hợp khi ID phải tồn tại theo business logic.
- Option A: Caller quên check →
null.foo→TypeErrorở xa nơi gốc. Bug âm thầm. Option B: Caller không catch → crash ngay tạifindUser. Crash sớm, dễ debug hơn.
Kết luận: Throw khi "not found" là điều bất thường và không thể tiếp tục. Return null khi "not found" là một branch hợp lệ. Đừng dùng throw cho flow control bình thường.
12. Production Scenario (Tình huống thực tế)
Context: Bạn đang viết một utility function formatCurrency dùng trong nhiều component.
function formatCurrency(amount, currency) {
return amount.toFixed(2) + " " + currency.toUpperCase();
}
// Một ngày nọ, API trả về:
formatCurrency(null, "usd");
// TypeError: Cannot read properties of null (reading 'toFixed')Symptom: Trang checkout crash. Stack trace chỉ đến formatCurrency nhưng không rõ tại sao amount là null.
Constraint: Không thể sửa API ngay. Phải làm formatCurrency defensive.
Câu hỏi:
- Tại sao lỗi xảy ra ở
formatCurrencythay vì nơi nhận data từ API? - Viết lại
formatCurrencyđể throw sớm với message rõ ràng nếu input không hợp lệ. - Lợi ích của việc throw sớm (fail fast) trong trường hợp này là gì?
[Đáp án tham khảo]
Bạn nghĩ:
formatCurrencygiả địnhamountlà number. Nó không validate input. Lỗi "nổ" ở utility function, xa nơi data bị corrupt.- js
function formatCurrency(amount, currency) { if (typeof amount !== "number") { throw new TypeError(`Expected number for amount, got ${typeof amount}`); } if (typeof currency !== "string") { throw new TypeError(`Expected string for currency, got ${typeof currency}`); } return amount.toFixed(2) + " " + currency.toUpperCase(); } - Throw sớm giúp stack trace chỉ đúng nơi data bị corrupt (hoặc nơi truyền sai). Không để lỗi lan truyền qua 5 tầng function rồi mới nổ ở một utility generic.
Bài học: "Fail fast" với throw + message rõ ràng giúp production debugging nhanh hơn gấp 10 lần so với để lỗi âm thầm lan truyền.
13. AI-assisted Exercise (Bài tập với AI)
Level B — Challenge
- Tự trả lời trước: Viết đoạn code và dự đoán:js
function check(x) { if (x < 0) throw "Negative"; return x; } try { check(-1); } catch (e) { console.log(e.stack); } - Hỏi AI: "Tại sao
e.stacklàundefined? Tôi đã throw một lỗi mà?" - So sánh câu trả lời AI với nhận định của bạn. AI có nhấn mạnh rằng primitive string không có
stackproperty và giải thích tại sao nên dùngnew Error()? - Verify bằng MDN: tìm "throw" trên MDN, đọc phần "Throwing a primitive".
Gợi ý
AI thường biết throw có thể ném primitive nhưng đôi khi giải thích mơ hồ bằng cách nói "throw can be any value". Nếu AI không chỉ ra rằng mất stack trace là hậu quả trực tiếp của throw primitive và điều này làm debug khó khăn trong production, bạn đã tìm ra điểm mù.
[Đáp án tham khảo]
Bạn nghĩ:
e.stacklàundefinedvìelà string"Negative", không phải Error object. String không có propertystack. Đây là lý do tại sao nên luônthrow new Error(...).AI trả lời (typical):
throwcan accept any expression, including primitives.- In your code,
eis the string"Negative". - Strings do not have a
stackproperty, soe.stackisundefined. - Best practice is to throw
Errorobjects.
So sánh: AI thường đúng về behavior nhưng đôi khi không giải thích tại sao điều này quan trọng trong production: nếu bạn throw primitive và có global error handler (Sentry, Rollbar, v.v.), tool đó sẽ không có stack trace để group lỗi hoặc trace về nguồn.
Điểm AI nói sai hoặc quá mơ hồ: "Best practice is to throw Error objects" — đúng nhưng AI hiếm khi nói rõ: "Nếu codebase của bạn có convention throw primitive, bạn đang làm mất khả năng debug của toàn team."
Kết luận: Nếu bạn chỉ ra được rằng
throw primitive= mất structured debugging information, bạn đã hiểu sâu hơn AI. Concept này sẽ quay lại ở Stage 3 (Promise rejection — luôn reject với Error object), Stage 9 (Error logging và monitoring), và Stage 13 (Incident response — stack trace là evidence).
14. Teach Back (Dạy lại)
Giả sử một junior developer hỏi bạn:
"Em thấy người ta bảo không nên
return nullđể báo lỗi mà nênthrow. Nhưng throw làm app crash mà? Tại sao lại tốt hơn?"
Hãy giải thích trong 2 phút, dùng đúng terminology: propagation, fail fast, stack trace, silent failure.
Mô phỏng
- Bạn nói:
throwkhông tự làm app crash — không ai bắt mới làm app crash. Và điều đó tốt hơnreturn nullrất nhiều.
Khi bạn return null để báo lỗi, caller nhận null và có thể nghĩ đó là kết quả hợp lệ. Rồi null đi qua 3 function nữa, cuối cùng null.name mới nổ TypeError. Lúc đó stack trace chỉ đến function cuối cùng — bạn mất dấu vết về nơi null xuất phát. Đây gọi là silent failure — lỗi âm thầm lan truyền.
Khi bạn throw, execution dừng ngay tại nơi phát hiện vấn đề. Error object chứa stack trace đầy đủ từ nơi throw qua các tầng function. Nếu bạn try/catch đúng chỗ (bài sau), bạn xử lý được. Nếu không, app crash ngay — nhưng crash sớm, crash gần nguồn, dễ debug hơn crash muộn ở xa.
💡 Tưởng tượng
return nullnhư một chiếc xe bị thủng lốp nhưng vẫn chạy — đến khi lật xe mới biết.thrownhư xe tự dừng ngay khi phát hiện lốp thủng — bạn biết ngay vấn đề ở đâu.
Gợi ý đánh giá bản thân
- Đồng nghiệp có hiểu tại sao crash sớm tốt hơn silent failure không?
- Bạn có tránh được việc nói "throw tốt hơn vì người ta bảo thế" không?
- Nếu đồng nghiệp hỏi: "Vậy khi nào em KHÔNG nên throw?" — bạn trả lời được không? (Gợi ý: khi "not found" là kết quả hợp lệ, hoặc khi bạn đang viết một hàm validation nhẹ và muốn trả về boolean.)
- Nếu đồng nghiệp viết
throw "error"— bạn giải thích được tại sao nên dùngnew Errorkhông?
15. Assessment (Đánh giá)
| Objective (Mục tiêu) | Hình thức đánh giá | Task |
|---|---|---|
| Throw dừng execution | Prediction (Dự đoán) | Prediction Câu 2, 4, 5 |
| Throw Error object | Implementation (Thực hành) | Implementation Lab Level 1–3 |
| Phân biệt throw primitive vs Error | AI-assisted | AI-assisted Section |
| Propagation qua call stack | Prediction (Dự đoán) | Prediction Câu 2 |
| Guard clause + throw | Implementation (Thực hành) | Implementation Lab Level 2–3 |
| Fail fast vs return null | Design Exercise | Design Exercise Section |
| Explain propagation | Teach Back | Teach Back Section |
16. Exit Criteria (Tiêu chí qua bài)
- [ ] Có thể giải thích
throwdừng execution và truyền value lên call stack. - [ ] Có thể sử dụng
throwvớiErrorobject để báo lỗi rõ ràng. - [ ] Có thể dự đoán luồng execution khi
throwxuất hiện trong nested calls (≥ 4/5 scenarios đúng). - [ ] Có thể phân biệt throw
Errorobject và throw primitive, và giải thích tại sao primitive là anti-pattern. - [ ] Có thể viết guard clause kết hợp
throwđể validate input sớm. - [ ] Có thể debug lỗi do quên từ khóa
throwtrướcnew Error. - [ ] Có thể giải thích trade-off giữa throw và return null trong design scenario.
17. Spiral Connection (Liên kết xoắn ốc)
Previous (Trước): Errors (0.7.1) — bạn đã biết error object chứa
message,name,stack. Giờ bạn học cách kích hoạt error object để nó di chuyển trong chương trình.
Current (Hiện tại): Throw —
throwstatement, propagation lên call stack, uncaught error, guard clause + throw, fail fast.
Next (Tiếp theo):
- 0.7.3 (Try/Catch) — Bắt error đang propagate.
try/catchlà "lưới" để bắt bóng lỗi trước khi nó thoát ra ngoài. Nếu không có try/catch, throw = crash.- 0.7.4 (Defensive Programming) — Kết hợp guard clause, throw, và validation để viết code chống chịu lỗi.
- Stage 1 (Execution Model) — Call stack. Propagation của throw chính là unwinding của call stack. Hiểu execution context giúp trace throw chính xác.
- Stage 3 (Async) —
throwtrong async function trở thành Promise rejection. Propagation khác synchronous.- Stage 8 (React) — Error Boundaries catch throw từ component tree. Throw trong render phase.
- Stage 9/13 (Production) — Global error handlers, logging services (Sentry), và incident response. Stack trace từ throw là evidence chính.