Lesson 0.7.3 — Try/Catch
0. Metadata
| Field | Value |
|---|---|
| Stage | 0 — JavaScript Language Foundation |
| Module | 0.7 — Error Handling & Code Quality |
| Lesson | 0.7.3 |
| Competency | C01.8 — Error Handling |
| Depth Target | L2–L3 |
| Prerequisites | Errors (0.7.1), Throw (0.7.2), Control Flow (0.4.x) |
| Estimated Cognitive Load | Medium–High |
1. Why This Exists (Vì sao cần học)
Bạn đã biết throw ném error lên call stack. Nếu không ai bắt, script crash. Nhưng trong production, "crash" không phải lúc nào cũng là lựa chọn tốt — đôi khi bạn cần bắt lỗi, ghi log, và tiếp tục hoặc bắt lỗi, dọn dẹp resource, và báo lỗi graceful.
Xét đoạn code:
function readConfig(path) {
const data = JSON.parse(readFile(path));
return data;
}
readConfig("app.json");
// Nếu file không tồn tại hoặc JSON sai → crash toàn bộ appVấn đề cốt lõi
throw là cơ chế propagation. Nhưng propagation không có điểm dừng thì thành crash. try/catch là điểm dừng có kiểm soát trên đường đi của error.
try/catch cho phép bạn:
- Bắt error tại một điểm cụ thể.
- Phân loại error để xử lý khác nhau.
- Dọn dẹp resource (file, network, timer) dù thành công hay thất bại.
- Rethrow nếu lỗi này không thuộc trách nhiệm của bạn.
Nhưng try/catch cũng là nguồn của một anti-pattern nguy hiểm: catch-all + silent failure. Bài này dạy bạn dùng try/catch như một cổng kiểm soát, không phải tấm chắn vô điều kiện.
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
name,message,stack(0.7.1). - Biết
throwdừng execution và propagate lên call stack (0.7.2). - Hiểu
if/elsevà block scope (0.4.2). - Biết
typeofvàinstanceof(0.2.5, preview Stage 2).
WARNING
Nếu bạn chưa chắc tại sao throw làm script crash khi không có try/catch, quay lại 0.7.2. try/catch chỉ có ý nghĩa khi bạn hiểu đường đi của error trên call stack.
3. Learning Objectives (Mục tiêu học tập)
Sau bài này, bạn có thể:
- Sử dụng
try/catchđể bắt error và ngăn script crash. - Sử dụng
finallyđể đảm bảo cleanup code chạy dù thành công hay lỗi. - Truy cập error object trong
catchvà đọcname,message,stack. - Phân loại error bằng
instanceofhoặcnameđể xử lý có chọn lọc. - Thực hiện rethrow khi error không thuộc phạm vi xử lý hiện tại.
- Nhận diện anti-pattern catch-all và silent failure.
- Dự đoán luồng execution khi có
try/catch/finallyvàthrow.
4. Mental Model (Mô hình tư duy)
Mental Model
Try/Catch = Cổng kiểm soát trên đường cao tốc của error
try {
// Đường cao tốc bình thường
riskyOperation();
successCode();
}
↓ throw xảy ra
catch (error) {
// Cổng chặn — bắt error đang bay lên
handle(error);
}
↓ (luôn chạy)
finally {
// Trạm dọn dẹp — chạy dù thành công hay lỗi
cleanup();
}
Propagation với Try/Catch
inner() throw
↓
middle() không catch → error bay qua
↓
outer() có catch → error dừng lại ở đây
↓
Nếu outer() rethrow → error tiếp tục bay lênQuy tắc vàng:
trybắt đầu một "vùng được bảo vệ". Nếu bất kỳ dòng nào trongtrythrow, execution nhảy ngay đếncatch.catchchỉ chạy khi có error. Nếutrythành công,catchbị bỏ qua.finallychạy luôn luôn — sautrythành công, saucatch, hoặc sautrycó return/throw (trừ khi process bị kill).catch (e)nhận error object. Bạn có thể inspecte.name,e.message,e.stack.- Nếu
catchkhông xử lý xong và không rethrow, error coi như "đã giải quyết" — không propagate tiếp.
5. Core Concepts (Các khái niệm cốt lõi)
Essential (Bắt buộc)
| Concept | Ý nghĩa |
|---|---|
try block | Vùng code có thể throw. Nếu throw, nhảy đến catch. |
catch (error) | Bắt error từ try. error là parameter chứa thrown value. |
finally block | Chạy luôn luôn sau try hoặc catch. Dùng cho cleanup. |
| Rethrow | catch xử lý một phần, rồi throw error; để propagate tiếp. |
| Phân loại lỗi | if (e instanceof TypeError) hoặc e.name === "TypeError" để xử lý có chọn lọc. |
| Catch-all anti-pattern | catch (e) { console.log(e); } — bắt mọi lỗi nhưng không xử lý thực sự. |
Supporting (Hỗ trợ)
| Concept | Ý nghĩa |
|---|---|
try...finally (không catch) | Chỉ dùng finally để cleanup mà không bắt lỗi. Lỗi vẫn propagate. |
Error trong catch/finally | Nếu catch hoặc finally throw error mới, error mới đè lên error cũ. |
Awareness (Biết tồn tại)
| Concept | Lý do chưa đào sâu |
|---|---|
| Conditional catch (optional binding) | catch { ... } (ES2019) — không cần parameter nếu không dùng error. |
Error.cause | Chain lỗi khi rethrow. ES2022. Stage 9/13. |
Out of Scope (Không thuộc bài này)
- Async
try/catchvớiawait(Promise rejection). Thuộc Stage 3. try/catchperformance cost trong hot path. Thuộc Stage 11.- Domain-specific error recovery strategies (retry, circuit breaker). Thuộc Stage 5/9.
6. Worked Example (Ví dụ phân tích từng bước)
Bài toán: Đọc và parse JSON từ input, xử lý các loại lỗi khác nhau.
function parseConfig(input) {
try {
const parsed = JSON.parse(input);
if (typeof parsed !== "object" || parsed === null) {
throw new TypeError("Config must be an object");
}
return parsed;
} catch (error) {
// instanceof kiểm tra error có phải instance của SyntaxError không
// (Cách khác không cần instanceof: error.name === "SyntaxError")
if (error instanceof SyntaxError) {
console.error("Invalid JSON format:", error.message);
return { default: true };
}
if (error instanceof TypeError) {
console.error("Wrong config type:", error.message);
return { default: true };
}
throw error; // Rethrow — không phải lỗi ta biết cách xử lý
} finally {
console.log("Parse attempt completed");
}
}
console.log(parseConfig('{"port": 3000}'));
console.log(parseConfig("not json"));Output:
Parse attempt completed
{ port: 3000 }
Invalid JSON format: Unexpected token 'o', "not json" is not valid JSON
Parse attempt completed
{ default: true }Walkthrough
Step 1 — try thành côngJSON.parse('{"port": 3000}') thành công. parsed là object. if sai. return parsed. catch bị bỏ qua. finally chạy.
Step 2 — try throw SyntaxErrorJSON.parse("not json") throw SyntaxError. Execution trong try dừng ngay lập tức. Nhảy đến catch (error).
Step 3 — Phân loại trong catcherror instanceof SyntaxError là true. Log message. Return fallback { default: true }. finally vẫn chạy sau catch.
Step 4 — Rethrow Nếu JSON.parse thành công nhưng parsed là number (ví dụ "123"), dòng throw new TypeError(...) trong try ném TypeError. catch bắt được, instanceof TypeError đúng → log và return fallback.
Nếu một lỗi khác xảy ra (ví dụ: RangeError từ một hàm khác), catch không match instanceof nào → throw error; rethrow. Error tiếp tục propagate lên.
Step 5 — finally luôn chạy Dù try thành công, catch chạy, hay catch rethrow — finally đều in "Parse attempt completed".
💡 Key Insight:
catchkhông phải là "bắt mọi thứ rồi nuốt".catchlà "bắt, phân loại, xử lý những gì mình hiểu, và trả lại những gì mình không hiểu". Rethrow là cách bảo vệ bạn khỏi việc âm thầm swallow lỗi nghiêm trọng.
7. Prediction Exercise (Bài tập dự đoán)
Đừng chạy code. Đọc và dự đoán:
- (1) Output là gì?
- (2)
finallychạy mấy lần? - (3) Có error nào thoát ra ngoài không?
Câu 1
function test() {
try {
return "try";
} finally {
console.log("finally");
}
}
console.log(test());Câu 2
function test() {
try {
throw new Error("A");
} catch (e) {
console.log("catch:", e.message);
} finally {
console.log("finally");
}
}
test();Câu 3
function test() {
try {
throw new Error("A");
} catch (e) {
throw new Error("B");
} finally {
console.log("finally");
}
}
try {
test();
} catch (e) {
console.log("outer:", e.message);
}Câu 4
let count = 0;
function risky() {
count++;
if (count === 1) throw new TypeError("fail");
return "ok";
}
try {
console.log(risky());
} catch (e) {
console.log("retry");
console.log(risky());
}Câu 5
try {
console.log("A");
} catch (e) {
console.log("B");
} finally {
console.log("C");
}Câu 6 (Transfer — Phân loại lỗi)
function process(data) {
try {
const parsed = JSON.parse(data);
return parsed.value.toUpperCase();
} catch (e) {
if (e instanceof SyntaxError) return "BAD_JSON";
return "UNKNOWN";
}
}
console.log(process('{"value": "hello"}'));
console.log(process("not json"));
console.log(process('{"value": 123}'));Câu hỏi:
- Dự đoán output của 3 lần gọi.
- Lần gọi thứ 3 trả về
"UNKNOWN"— điều này có phải là xử lý đúng không? Tại sao? Nếu không, nên sửa như thế nào?
[Đáp án & Giải thích]
Câu 1:
finally
try- Giải thích:
finallychạy trướcreturnthực sự thoát khỏi function.return "try"được "tạm giữ",finallychạy xong mới return. Đây là behavior đặc biệt củafinally.
Câu 2:
catch: A
finally- Giải thích:
trythrow →catchbắt được, log"catch: A".finallychạy saucatch. Không có error thoát ra ngoài.
Câu 3:
finally
outer: B- Giải thích:
trythrow"A"→catchbắt, nhưngcatchlại throw"B".finallyvẫn chạy trước khi error"B"thoát ra. Outercatchbắt"B". Error"A"bị thay thế (replaced) bởi"B"— JavaScript engine chỉ giữ completion value mới nhất, stack trace của"A"bị discard. Đây là điểm mù nguy hiểm của throw trongcatch.
Câu 4:
retry
ok- Giải thích: Lần 1
risky()throwTypeError.catchlog"retry". Lần 2risky()count === 2, return"ok".catchkhông bắt lần 2 vì lần 2 không throw.
Câu 5:
A
C- Giải thích:
trythành công, không có lỗi.catchbị bỏ qua.finallychạy.
Câu 6:
HELLO
BAD_JSON
UNKNOWNGiải thích:
JSON.parsethành công,parsed.valuelà"hello",.toUpperCase()→"HELLO".JSON.parsethrowSyntaxError→catchtrả về"BAD_JSON".JSON.parsethành công ({"value": 123}), nhưngparsed.valuelà123(number).123.toUpperCase()throwTypeError.catchkhông checkinstanceof TypeErrornên rơi vàoreturn "UNKNOWN".
Vấn đề lần gọi thứ 3: Trả về
"UNKNOWN"che giấu mộtTypeErrorthực sự — đây là silent failure. Nên sửa:jscatch (e) { if (e instanceof SyntaxError) return "BAD_JSON"; throw e; // Rethrow TypeError để caller biết }
Transfer Check
Bạn chưa từng gặp đoạn code sau. Dựa vào mental model, hãy trả lời:
function loadData(source) {
let connection;
try {
connection = openConnection(source);
const data = connection.read();
return data;
} catch (e) {
console.error("Load failed:", e.message);
return null;
} finally {
if (connection) connection.close();
}
}- Nếu
openConnectionthrow,connection.close()trongfinallycó chạy không? Tại sao cần checkif (connection)? - Nếu
connection.read()throw,connection.close()có chạy không? - Nếu mọi thứ thành công,
connection.close()có chạy không?
[Đáp án & Giải thích]
- Có chạy, nhưng
connectionlàundefined(vìopenConnectionthrow trước khi gán). Nếu không checkif (connection),undefined.close()sẽ gâyTypeErrormới trongfinally— đè lên error gốc. Đây là bug production phổ biến. - Có chạy.
read()throw →catchchạy →finallychạy.connectionđã được gán nênclose()an toàn. - Có chạy.
trythành công,return datađược "tạm giữ",finallychạy, rồi mới thực sự return.
Bài học: finally là nơi cleanup, nhưng phải defensive vì bạn không biết try đã chạy đến đâu trước khi lỗi.
8. Implementation Lab (Bài lab thực hành)
Level 1 — Guided (Có hướng dẫn)
Viết function safeParse dùng try/catch để parse JSON. Nếu lỗi, trả về null. Luôn log "Parsing done" dù thành công hay thất bại.
function safeParse(json) {
try {
return _______;
} catch (_______) {
return _______;
} finally {
_______;
}
}
console.log(safeParse('{"a": 1}')); // { a: 1 }
console.log(safeParse("bad")); // nullGợi ý
return JSON.parse(json);, catch (e), return null;, console.log("Parsing done").
[Đáp án tham khảo]
function safeParse(json) {
try {
return JSON.parse(json);
} catch (e) {
return null;
} finally {
console.log("Parsing done");
}
}
console.log(safeParse('{"a": 1}')); // { a: 1 }
// Parsing done
console.log(safeParse("bad")); // null
// Parsing doneGiải thích:
tryparse JSON. Thành công → return kết quả.- Lỗi →
catchbắt, returnnull. finallychạy trong cả hai trường hợp.
Level 2 — Partial Scaffold (Khung sẵn)
Hoàn thành function calculate để bắt TypeError và RangeError riêng biệt, rethrow các lỗi khác.
function calculate(a, b, op) {
try {
if (typeof a !== "number" || typeof b !== "number") {
throw new TypeError("Operands must be numbers");
}
if (op === "/" && b === 0) {
throw new RangeError("Division by zero");
}
if (op === "+") return a + b;
if (op === "-") return a - b;
if (op === "*") return a * b;
if (op === "/") return a / b;
throw new Error("Unknown operator");
} catch (_______) {
if (_______ instanceof TypeError) {
return "TYPE_ERROR";
}
if (_______ instanceof RangeError) {
return "RANGE_ERROR";
}
_______; // Rethrow lỗi khác
}
}
// Test:
console.log(calculate(10, 2, "+")); // 12
console.log(calculate("10", 2, "+")); // TYPE_ERROR
console.log(calculate(10, 0, "/")); // RANGE_ERROR
// console.log(calculate(10, 2, "%")); // Error: Unknown operator[Đáp án tham khảo]
function calculate(a, b, op) {
try {
if (typeof a !== "number" || typeof b !== "number") {
throw new TypeError("Operands must be numbers");
}
if (op === "/" && b === 0) {
throw new RangeError("Division by zero");
}
if (op === "+") return a + b;
if (op === "-") return a - b;
if (op === "*") return a * b;
if (op === "/") return a / b;
throw new Error("Unknown operator");
} catch (e) {
if (e instanceof TypeError) {
return "TYPE_ERROR";
}
if (e instanceof RangeError) {
return "RANGE_ERROR";
}
throw e; // Rethrow lỗi khác
}
}Giải thích:
catch (e)bắt mọi lỗi từtry.instanceofphân loại để xử lý có chọn lọc.throw e;ở cuốicatchđảm bảo lỗi không thuộc hai loại trên vẫn propagate ra ngoài. Không bị silent failure.
Level 3 — Independent (Tự viết)
Viết các function sau:
// 1. safeRead(obj, key) → Trả về obj[key]. Nếu obj là null/undefined,
// không throw mà trả về undefined. Dùng try/catch.
// 2. withCleanup(task, cleanup) → Chạy task() trong try. Dù task() throw hay return,
// luôn gọi cleanup() trong finally. Trả về kết quả của task() hoặc throw nếu task() throw.
// 3. retryOnce(fn) → Gọi fn() trong try. Nếu throw, gọi lại fn() một lần nữa.
// Nếu lần 2 vẫn throw, để lỗi thoát ra. Không dùng vòng lặp.
// Sử dụng:
const data = { name: "Alice" };
console.log(safeRead(data, "name")); // "Alice"
console.log(safeRead(null, "name")); // undefined
let cleaned = false;
const result = withCleanup(
() => "done",
() => { cleaned = true; }
);
console.log(result, cleaned); // "done" true
let calls = 0;
const flaky = () => {
calls++;
if (calls === 1) throw new Error("fail");
return "ok";
};
console.log(retryOnce(flaky)); // "ok"
console.log(calls); // 2[Đáp án tham khảo]
function safeRead(obj, key) {
try {
return obj[key];
} catch (e) {
return undefined;
}
}
function withCleanup(task, cleanup) {
try {
return task();
} finally {
cleanup();
}
}
function retryOnce(fn) {
try {
return fn();
} catch (e) {
return fn(); // Lần 2, nếu throw sẽ thoát ra ngoài
}
}Giải thích:
safeRead:null[key]throwTypeError.catchtrả vềundefined— defensive pattern khi access external data.- ⚠️ Lưu ý: Đây là catch-all đơn giản. Nếu
objlà object hợp lệ nhưng getter trênkeythrow lỗi, function cũng trả vềundefined. Ở production, nên phân loạie.namecụ thể hơn thay vì bắt tất cả. withCleanup:finallyđảm bảocleanup()chạy dùtask()return hay throw. Không cầncatchnếu chỉ muốn cleanup và propagate lỗi.retryOnce: Lần 1 throw →catchchạy, gọi lạifn(). Lần 2 thành công → return. Lần 2 throw → không cócatchnào khác → propagate.
9. Edge Cases (Các trường hợp ngoại lệ)
Catch-all + silent failure
try {
riskyOperation();
} catch (e) {
// Không làm gì cả
}Tại sao: Bắt mọi lỗi nhưng không log, không xử lý, không rethrow. Bug nghiêm trọng bị chôn vùi.
Cách nhận biết: Code chạy "bình thường" nhưng kết quả sai. Không có trace để debug.
Cách xử lý: Ít nhất phải log. Tốt hơn là phân loại và chỉ catch những lỗi bạn hiểu.
catch (e) {
if (e instanceof KnownError) {
handle(e);
} else {
throw e;
}
}Throw trong catch ghi đè error gốc
try {
throw new Error("Original");
} catch (e) {
throw new Error("New");
}Tại sao: Error "Original" bị mất hoàn toàn. Stack trace chỉ còn "New".
Cách nhận biết: Stack trace không chỉ đến nguồn lỗi thực sự.
Cách xử lý: Nếu cần wrap error, giữ lại thông tin gốc (ES2022 cause, hoặc log trước khi throw):
catch (e) {
console.error("Original:", e);
throw new Error("New: " + e.message);
}finally chạy trước return
function test() {
try {
return "try";
} finally {
return "finally";
}
}
console.log(test()); // "finally"Tại sao: finally có thể override return từ try hoặc catch. Đây là behavior ít người biết.
Cách nhận biết: Return value bất ngờ.
Cách xử lý: Không return trong finally.
catch parameter scope
try {
throw new Error("x");
} catch (e) {
console.log(e.message);
}
console.log(e); // ReferenceError: e is not definedTại sao: e chỉ tồn tại trong block catch.
Cách nhận biết: ReferenceError khi access e ngoài catch.
Cách xử lý: Nếu cần error bên ngoài, gán vào biến outer scope trong catch.
10. Debug Lab (Bài lab gỡ lỗi)
Symptom (Triệu chứng): App không crash nhưng data bị mất không lý do.
function saveUser(user) {
try {
validate(user);
db.insert(user);
return true;
} catch (e) {
console.log("Error saved");
return false;
}
}
const result = saveUser(null);
console.log(result); // false
// Không biết tại sao failReproduction (Tái hiện lỗi): Chạy saveUser(null).
Evidence (Bằng chứng):
- Console chỉ in
"Error saved". Không có stack trace. Không biết lỗi gì. validate(null)có thể throwTypeErrorhoặcReferenceError. Hoặcdb.insert(null)throw. Hoàn toàn không rõ.catchbắt mọi thứ và chỉ log một dòng generic.
Hypothesis (Giả thuyết): Developer sợ app crash nên bọc try/catch quá rộng. Họ nghĩ "bắt hết là an toàn". Nhưng điều này tạo ra silent failure — lỗi bị nuốt mà không có thông tin để debug.
Verification (Xác minh):
// Thêm log tạm để xem error thực sự:
catch (e) {
console.log("ACTUAL ERROR:", e.name, e.message, e.stack);
}Root Cause (Nguyên nhân gốc rễ): Catch-all không phân loại. Không rethrow lỗi không mong đợi. console.log("Error saved") không chứa bất kỳ thông tin debug nào.
Fix (Sửa):
function saveUser(user) {
try {
validate(user);
db.insert(user);
return true;
} catch (e) {
if (e instanceof ValidationError) {
console.error("Validation failed:", e.message);
return false;
}
throw e; // Lỗi DB hoặc lỗi không mong đợi → propagate
}
}Hoặc ở hiện tại Stage 0 (chưa có custom class):
function saveUser(user) {
try {
validate(user);
db.insert(user);
return true;
} catch (e) {
if (e.name === "ValidationError") {
console.error("Validation failed:", e.message);
return false;
}
console.error("Unexpected error during save:", e);
throw e;
}
}Ghi chú:
instanceof ValidationErrorchỉ hoạt động nếu bạn định nghĩa custom classValidationError(Stage 2). Ở Stage 0, dùnge.nameđể phân loại.
Prevention (Phòng ngừa):
- Catch chỉ những lỗi bạn biết cách xử lý.
- Nếu catch generic, phải log đầy đủ
name,message,stack. - Luôn để lối thoát cho lỗi không mong đợi:
throw eở cuốicatch. - Hỏi: "Nếu lỗi này xảy ra lúc 3 giờ sáng, tôi có đủ thông tin để debug không?"
11. Design Exercise (Bài tập thiết kế giải pháp)
Bạn cần viết hàm fetchData.
function fetchData(url) {
try {
return httpGet(url);
} catch (e) {
return { error: true, data: null };
}
}function fetchData(url) {
try {
return httpGet(url);
} catch (e) {
if (e.name === "NetworkError") {
return { error: true, data: null };
}
throw e;
}
}Câu hỏi:
- Option A có lợi gì về đơn giản? Rủi ro gì trong production?
- Option B có lợi gì khi debug incident?
- Nếu
httpGetthrowTypeErrordo bug code (ví dụ: gọi.trim()trênundefined), option nào giúp phát hiện sớm hơn?
[Đáp án tham khảo]
Bạn nghĩ:
- Option A: Đơn giản, caller luôn nhận về object có shape cố định. Rủi ro: Bug nghiêm trọng (TypeError, ReferenceError) bị nuốt. App trả về
{ error: true }thay vì crash — bạn không biết có bug cho đến khi user báo. - Option B: Chỉ swallow lỗi network (expected). Các lỗi code (unexpected) vẫn propagate và được monitoring tool bắt. Dễ phân biệt "lỗi mạng" và "lỗi code".
- Option B.
TypeErrorsẽ thoát ra ngoài, được log với stack trace đầy đủ. Option A sẽ trả về{ error: true }và bạn mất dấu vết bug.
- Option A: Đơn giản, caller luôn nhận về object có shape cố định. Rủi ro: Bug nghiêm trọng (TypeError, ReferenceError) bị nuốt. App trả về
Kết luận: Catch chỉ những gì bạn dự đoán được và có kế hoạch xử lý. Mọi thứ khác nên được biết đến (crash + log) thay vì bị chôn vùi.
12. Production Scenario (Tình huống thực tế)
Context: Bạn đang viết một batch processor xử lý 1000 records.
function processRecords(records) {
const results = [];
for (const record of records) {
try {
const result = transform(record);
results.push(result);
} catch (e) {
// Skip bad records
}
}
return results;
}Symptom: Sau khi chạy, chỉ có 800 kết quả. 200 records bị "skip" nhưng không biết tại sao. Không có log.
Constraint: Không thể dừng toàn bộ batch vì một vài records lỗi. Nhưng cần biết records nào lỗi để investigate.
Câu hỏi:
- Tại sao empty
catchlà nguy hiểm trong production? - Viết lại để vẫn skip bad records nhưng log đủ thông tin (record index, error message).
- Nếu một record gây lỗi không phải do data mà do bug code (ví dụ:
transformcóReferenceError), bạn có muốn skip không?
[Đáp án tham khảo]
Bạn nghĩ:
- Empty catch = silent failure. Bạn mất 20% data mà không biết lý do. Không thể reproduce.
- js
function processRecords(records) { const results = []; for (let i = 0; i < records.length; i++) { try { const result = transform(records[i]); results.push(result); } catch (e) { console.error(`Record ${i} failed:`, e.message, e.stack); // Hoặc push vào dead-letter queue } } return results; } - Không.
ReferenceErrorlà bug code, không phải bad data. Nên rethrow hoặc dừng batch để fix code. Chỉ nên catch và skip lỗi domain-related (ví dụ:ValidationError).
Bài học: Trong production, "không crash" không đồng nghĩa "hoạt động đúng". Empty catch biến "hoạt động sai" thành "hoạt động im lặng".
13. AI-assisted Exercise (Bài tập với AI)
Level B — Challenge
- Tự trả lời trước: Dự đoán output:js
function test() { try { return "A"; } catch (e) { return "B"; } finally { console.log("C"); } } console.log(test()); - Hỏi AI: "Tại sao
finallychạy trước khireturn 'A'thực sự thoát function? Điều này có nghĩa làfinallycó thể thay đổi return value không?" - 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
finallychạy trước return thực sự và có thể override return value? - Verify bằng MDN: tìm "try...catch" trên MDN, đọc phần "The finally block".
Gợi ý
AI thường biết finally "luôn chạy" nhưng đôi khi giải thích mơ hồ bằng cách nói "finally executes after try and catch". Nếu AI không chỉ ra rằng finally chạy trước khi control trả về caller (và có thể override return/throw), bạn đã tìm ra điểm mù. AI cũng có thể bỏ qua việc finally có return sẽ override try return.
[Đáp án tham khảo]
Bạn nghĩ: Output là
"C"rồi"A".finallychạy trước khireturn "A"thực sự thoát function. Nếufinallycóreturn "D", kết quả sẽ là"D"—finallycó thể override.AI trả lời (typical):
- The
finallyblock executes after thetryandcatchblocks. - It runs regardless of whether an exception was thrown.
- If
finallycontains areturn, it can override the return value fromtry.
- The
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 viết cleanup trong
finallyvà vô tìnhreturnmột giá trị từ đó, bạn sẽ gây bug khó trace.Điểm AI nói sai hoặc quá mơ hồ: "finally executes after try and catch" — đúng nhưng dễ hiểu nhầm là "sau khi function đã return". AI hiếm khi nói rõ: "
finallychạy trong cùng một execution context, trước khi control truyền về caller."Kết luận: Nếu bạn chỉ ra được rằng
finallylà một completion record interceptor (trong spec terms) và có thể thay đổi kết quả củatry/catch, bạn đã hiểu sâu hơn AI. Concept này sẽ quay lại ở Stage 1 (Execution Model — completion records), Stage 3 (Async cleanup vớifinallytrong Promise), và Stage 9 (Resource management pattern).
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 bọc code trong try/catch để app không crash. Vậy em bọc toàn bộ app trong một try/catch lớn có được không?"
Hãy giải thích trong 2 phút, dùng đúng terminology: catch-all, silent failure, rethrow, phân loại lỗi.
Mô phỏng
- Bạn nói: Không được. Bọc toàn bộ app trong một
try/catchlớn giống như lắp một cái máy hút bụi khổng lồ trên trần nhà — mọi thứ rơi xuống đều biến mất mà bạn không biết là cái gì.
try/catch không phải để "ngăn crash bằng mọi giá". Nó là để bắt lỗi bạn hiểu và có kế hoạch xử lý. Ví dụ: bạn biết API có thể timeout → catch NetworkError và retry. Nhưng nếu bạn catch cả TypeError do bug code, bạn đang nuốt lỗi (silent failure).
Cách đúng:
- Catch những lỗi expected và recoverable.
- Log đầy đủ
name,message,stack. - Nếu không hiểu lỗi → rethrow để monitoring tool bắt và báo động.
💡 Tưởng tượng
try/catchnhư bộ lọc nước. Bạn không lắp bộ lọc để biến nước bẩn thành nước sạch mà không biết bẩn ở đâu. Bạn lắc bộ lọc để tách cặn bã (lỗi expected) và báo động nếu phát hiện chì (lỗi nghiêm trọng).
Gợi ý đánh giá bản thân
- Đồng nghiệp có hiểu tại sao catch-all là anti-pattern không?
- Bạn có tránh được việc nói "try/catch tốt hơn không có" một cách tuyệt đối không?
- Nếu đồng nghiệp hỏi: "Vậy em nên catch ở đâu?" — bạn trả lời được không? (Gợi ý: ở boundary — API call, file I/O, user input — nơi bạn có thông tin để phân loại.)
- Nếu đồng nghiệp viết
catch (e) { console.log(e) }— bạn giải thích được tại sao nên loge.stackvà rethrow nếu không xử lý không?
15. Assessment (Đánh giá)
| Objective (Mục tiêu) | Hình thức đánh giá | Task |
|---|---|---|
| Try/catch syntax | Implementation (Thực hành) | Implementation Lab Level 1 |
| Finally behavior | Prediction (Dự đoán) | Prediction Câu 1, 2, 3 |
| Error object trong catch | Implementation (Thực hành) | Implementation Lab Level 2 |
| Phân loại lỗi (instanceof) | Implementation (Thực hành) | Implementation Lab Level 2 |
| Rethrow | Implementation (Thực hành) | Implementation Lab Level 2–3 |
| Catch-all anti-pattern | Debug Lab / Design Exercise | Debug Lab & Design Exercise |
| Silent failure | Debug Lab | Debug Lab Section |
| Explain finally override | AI-assisted / Prediction | AI-assisted Section, Prediction Câu 1 |
| Explain catch-all anti-pattern | Teach Back | Teach Back Section |
16. Exit Criteria (Tiêu chí qua bài)
- [ ] Có thể sử dụng
try/catchđể bắt error và ngăn script crash. - [ ] Có thể sử dụng
finallyđể đảm bảo cleanup code chạy dù thành công hay lỗi. - [ ] Có thể truy cập và đọc
name,message,stacktừ error object trongcatch. - [ ] Có thể phân loại error bằng
instanceofhoặcnameđể xử lý có chọn lọc. - [ ] Có thể thực hiện rethrow khi error không thuộc phạm vi xử lý hiện tại.
- [ ] Có thể nhận diện và tránh catch-all anti-pattern.
- [ ] Có thể debug lỗi do empty catch hoặc catch quá rộng gây silent failure.
- [ ] Có thể dự đoán luồng execution khi có
try/catch/finallyvàthrow(≥ 4/5 scenarios đúng).
17. Spiral Connection (Liên kết xoắn ốc)
Previous (Trước): Throw (0.7.2) — bạn đã biết
throwném error lên call stack. Giờ bạn học cách dừng error đang bay bằngtry/catchvà dọn dẹp bằngfinally.
Current (Hiện tại): Try/Catch — bắt error, phân loại, rethrow, cleanup với finally, anti-pattern catch-all.
Next (Tiếp theo):
- 0.7.4 (Defensive Programming) — Kết hợp guard clause, validation, và
try/catchcó chọn lọc để viết code chống chịu lỗi.- 0.7.5 (Readable JavaScript) — Error handling là một phần của readability. Code xử lý lỗi rõ ràng dễ maintain hơn code "bọc try/catch khắp nơi".
- Stage 1 (Execution Model) — Call stack unwinding khi throw.
try/catchtạo một "catching boundary" trên stack. Completion records (return, throw, normal) trongfinally.- Stage 3 (Async) —
try/catchvớiawait. Promise rejection đượccatchnhư synchronous throw.finallytrong async function.- Stage 8 (React) — Error Boundaries là
try/catchở component level. Catch render error và hiển thị fallback.- Stage 9/13 (Production) — Structured logging, error aggregation (Sentry), incident response.
try/catchphải log đầy đủ context để trace production issue.