Skip to content

Lesson 0.7.1 — Errors ​

Bài 0.7.1 — Errors
Error object, Message, Stack & Built-in errors • 31 phút
0:00 / 0:00

0. Metadata ​

FieldValue
Stage0 — JavaScript Language Foundation
Module0.7 — Error Handling & Code Quality
Lesson0.7.1
CompetencyC01.8 — Error Handling
Depth TargetL2–L3
PrerequisitesValues & Types (0.2.1), Objects (0.6.5), Functions (0.5.1)
Estimated Cognitive LoadMedium

1. Why This Exists (Vì sao cần học) ​

Bạn viết code, chạy, và console hiện ra:

text
TypeError: Cannot read properties of undefined (reading 'name')
    at getUserName (app.js:15:21)
    at renderProfile (app.js:28:12)

Phản ứng phổ biến của Junior:

text
→ Copy toàn bộ dòng lỗi
→ Paste lên Google
→ Thử từng solution trên Stack Overflow
→ Sửa đến khi lỗi biến mất

Vấn đề cốt lõi

Error không phải là "một thứ đỏ đỏ cần làm biến mất". Error là một object chứa thông tin có cấu trúc:

  • What — chuyện gì xảy ra (message)
  • Where — xảy ra ở đâu (stack)
  • What kind — loại lỗi gì (name / constructor)

Nếu bạn không đọc được thông tin này, bạn sẽ tốn 30 phút Google một câu trả lời mà stack trace đã nói rõ trong 3 giây. Bài này dạy bạn đối xử với error như dữ liệu debug, không phải kẻ thù.

2. Prerequisites (Yêu cầu đầu vào) ​

Trước khi học bài này, bạn cần:

  • Biết object có property và method (0.6.5).
  • Biết typeof trả về "object" cho object (0.2.5).
  • Biết function call và argument passing (0.5.1).
  • Hiểu undefined và null là giá trị hợp lệ trong JavaScript (0.2.3).

WARNING

Nếu bạn chưa chắc tại sao typeof null là "object" nhưng null không có property, quay lại 0.2.5. Điều này liên quan trực tiếp đến lỗi "Cannot read properties of null".

3. Learning Objectives (Mục tiêu học tập) ​

Sau bài này, bạn có thể:

  1. Giải thích error là một object chứa message, stack, và name.
  2. Phân biệt 5 built-in error type phổ biến: Error, TypeError, ReferenceError, RangeError, SyntaxError.
  3. Dự đoán error type nào sẽ xuất hiện từ một đoạn code lỗi.
  4. Đọc một stack trace cơ bản để xác định file, function, và dòng gây lỗi.
  5. Tạo một Error object với message mô tả rõ ràng.
  6. Nhận diện sự khác biệt giữa runtime error và parse-time error.

4. Mental Model (Mô hình tư duy) ​

Mental Model

Error = Object chứa thông tin về sự cố

  name        → Loại lỗi (Error, TypeError, ReferenceError...)
  message     → Mô tả ngắn gọn vấn đề
  stack       → "Dấu vết" các function call dẫn đến lỗi

Stack Trace = Breadcrumb trail
  at getUserName (app.js:15:21)
       ↓
  Function getUserName, file app.js, dòng 15, cột 21
       ↓
  Là nơi lỗi "nổ"
       ↓
  Dòng dưới là function gọi getUserName
  Dòng tiếp là function gọi function đó...

Quy tắc vàng:

  • Error là value. Bạn có thể gán nó vào biến, truyền nó như argument, inspect property của nó.
  • message là string. stack là string (format phụ thuộc engine). name là string khớp với tên constructor.
  • Không phải mọi lỗi đều có thể "bắt" bằng try/catch (ví dụ: SyntaxError parse-time).
  • throw có thể ném bất kỳ value nào, nhưng best practice là ném Error object.

5. Core Concepts (Các khái niệm cốt lõi) ​

Essential (Bắt buộc) ​

ConceptÝ nghĩa
ErrorConstructor cơ bản. new Error("something went wrong").
messageString mô tả lỗi. Đọc dòng đầu tiên của stack trace.
stackString chứa lịch sử call stack. Giúp trace ngược về nguồn lỗi.
nameTên loại lỗi. "Error", "TypeError", "ReferenceError"...
TypeErrorThao tác không hợp lệ trên một kiểu dữ liệu. Ví dụ: null.foo, undefined()
ReferenceErrorTham chiếu đến biến không tồn tại. Ví dụ: console.log(notDeclared)
RangeErrorGiá trị nằm ngoài phạm vi cho phép. Ví dụ: new Array(-1)
SyntaxErrorCode vi phạm grammar của JavaScript. Thường xảy ra ở parse time.

Supporting (Hỗ trợ) ​

ConceptÝ nghĩa
throwNém một value (thường là Error object). Preview cho 0.7.2.
Parse-time vs RuntimeSyntaxError thường xảy ra trước khi code chạy. Các lỗi khác xảy ra khi chạy.

Awareness (Biết tồn tại) ​

ConceptLý do chưa đào sâu
Error.causeES2022. Cho phép chain lỗi. Học khi cần production error wrapping (Stage 9/13).
AggregateErrorGộp nhiều lỗi. Dùng với Promise.any. Thuộc Stage 3 (Async).
Engine-specific stack formatV8, SpiderMonkey, JSC format stack khác nhau. Không cần đọc spec ở Stage 0.

Out of Scope (Không thuộc bài này) ​

  • Custom error class (class MyError extends Error). Thuộc Stage 2 (Object Model / Class).
  • try/catch/finally mechanism. Thuộc 0.7.3.
  • Async error handling (Promise rejection, unhandled rejection). Thuộc Stage 3.
  • React Error Boundary. Thuộc Stage 8.

6. Worked Example (Ví dụ phân tích từng bước) ​

Bài toán: Đọc và hiểu một stack trace từ lỗi thực tế.

js
function getUserName(user) {
  return user.profile.name;
}

function renderProfile(user) {
  const name = getUserName(user);
  return `<h1>${name}</h1>`;
}

const user = { id: 1 };
console.log(renderProfile(user));

Output (V8/Node.js):

text
TypeError: Cannot read properties of undefined (reading 'name')
    at getUserName (app.js:2:21)
    at renderProfile (app.js:6:20)
    at Object.<anonymous> (app.js:10:13)

Walkthrough

Step 1 — Xác định Error TypeTypeError. Điều này nghĩa là: JavaScript đang cố thực hiện một thao tác không hợp lệ trên một kiểu dữ liệu. Không phải biến không tồn tại (ReferenceError), cũng không phải cú pháp sai (SyntaxError).

Step 2 — Đọc MessageCannot read properties of undefined (reading 'name'). JavaScript đang cố truy cập .name trên một giá trị undefined. Giá trị undefined này là user.profile, vì user = { id: 1 } không có property profile.

Step 3 — Đọc Stack Trace (từ trên xuống)

  • at getUserName (app.js:2:21) — Lỗi nổ tại dòng 2, cột 21, trong function getUserName. Đây là điểm gốc.
  • at renderProfile (app.js:6:20) — getUserName được gọi từ renderProfile, dòng 6.
  • at Object.<anonymous> (app.js:10:13) — renderProfile được gọi từ top-level, dòng 10.

💡 Key Insight: Stack trace là một snapshot đóng băng tại thời điểm error được tạo. Nó không thay đổi sau đó, dù có function nào khác chạy tiếp. Điều này quan trọng khi bạn log error async — stack trace ghi lại chính xác trạng thái call stack lúc lỗi xảy ra.

Step 4 — Kết luận Không cần Google. Không cần đoán. Dữ liệu đã nói rõ: user thiếu profile. Fix là đảm bảo user có cấu trúc đúng hoặc guard trước khi access.

Key Insight: Stack trace đọc từ trên xuống dưới (gần nhất → xa nhất). Dòng đầu tiên sau message là nơi lỗi thực sự xảy ra.

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ó, error type và message gì?
  • (3) Tại sao?

Câu 1 ​

js
const user = null;
console.log(user.name);

Câu 2 ​

js
const err = new Error("Connection failed");
console.log(typeof err);
console.log(err.message);

Câu 3 ​

js
function divide(a, b) {
  if (b === 0) {
    throw new RangeError("Division by zero");
  }
  return a / b;
}
divide(10, 0);

Câu 4 ​

js
console.log(score);

Câu 5 ​

js
const data = {};
data.settings.theme = "dark";

Câu 6 (Transfer — Parse-time vs Runtime) ​

Đoạn code sau thuộc file app.js:

js
function greet() {
  return "hello"
}

const user = {
  name: "Alice"
  age: 30
};

greet();

Câu hỏi:

  1. Đoạn code trên có chạy được không? Nếu không, lỗi gì xuất hiện ở phase nào (parse-time hay runtime)?
  2. try { ... } catch (e) { ... } bao quanh toàn bộ file có bắt được lỗi này không? Tại sao?
[Đáp án & Giải thích]

Câu 1: TypeError: Cannot read properties of null (reading 'name')

  • Giải thích: user là null. Truy cập .name trên null là thao tác không hợp lệ. null không có property. Đây là TypeError, không phải ReferenceError, vì user đã được khai báo (bằng const).

Câu 2: "object" "Connection failed"

  • Giải thích: new Error(...) tạo một object. typeof object là "object". err.message là string truyền vào constructor. Không có lỗi xảy ra.

Câu 3: RangeError: Division by zero

  • Giải thích: throw ném Error object ra ngoài. Vì đây là RangeError custom message, console sẽ in RangeError: Division by zero cùng stack trace. Lưu ý: 10 / 0 trong JavaScript thực ra trả về Infinity, không throw. Nhưng ở đây ta throw explicit.

Câu 4: ReferenceError: score is not defined

  • Giải thích: score chưa được khai báo. JavaScript không tìm thấy binding nào tên score trong scope. Đây là ReferenceError, khác với Câu 1 nơi biến đã tồn tại nhưng giá trị là null.

Câu 5: TypeError: Cannot set properties of undefined (setting 'theme')

  • Giải thích: data.settings là undefined (vì data không có property settings). Gán .theme trên undefined gây TypeError. Đây là lỗi phổ biến khi access nested property mà level trung gian missing.

Câu 6:

  • Giải thích:

    1. Không chạy được. SyntaxError — thiếu dấu phẩy sau "Alice". Lỗi xảy ra ở parse-time (trước khi dòng đầu tiên thực thi).
    2. Không. try/catch chỉ bắt lỗi runtime. Vì engine parse toàn bộ file trước khi chạy, SyntaxError ở parse-time làm crash script trước khi execution context được tạo.

    Key Insight: Đây là lý do tại sao SyntaxError thường "không thể xử lý graceful" trong production — nó ngăn cả file chạy, không giống runtime error có thể catch và recover.


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:

js
const response = JSON.parse('{"status": 200}');
const timestamp = response.data.createdAt;
  1. Nếu đoạn code trên lỗi, dự đoán error type và message.
  2. response có phải là undefined không? Tại sao điều này quan trọng để phân biệt TypeError và ReferenceError?
[Đáp án & Giải thích]
  1. TypeError: Cannot read properties of undefined (reading 'createdAt'). JSON.parse thành công nên response là object, nhưng response.data là undefined (vì JSON không có field data). Access .createdAt trên undefined gây TypeError.
  2. response không phải undefined. Nếu response là undefined, lỗi sẽ là ReferenceError: response is not defined (nếu chưa khai báo) hoặc TypeError khác. Việc phân biệt "biến chưa khai báo" (ReferenceError) với "biến đã tồn tại nhưng property là undefined" (TypeError) là kỹ năng đọc lỗi cốt lõi.

Bài học: Đừng nhìn TypeError và nghĩ "sai kiểu dữ liệu" một cách mơ hồ. Hãy đọc tiếp message: "Cannot read properties of undefined" nghĩa là giá trị trước dấu chấm là undefined, không phải biến không tồn tại.

8. Implementation Lab (Bài lab thực hành) ​

Level 1 — Guided (Có hướng dẫn) ​

Tạo một Error object và inspect các property của nó.

js
const error = new Error("File not found");

console.log(error.name);    // ?
console.log(error.message); // ?
console.log(typeof error.stack); // ?

Gợi ý

new Error("...") tạo object với name mặc định là "Error", message là string truyền vào, stack là string chứa trace.

[Đáp án tham khảo]
js
const error = new Error("File not found");

console.log(error.name);    // "Error"
console.log(error.message); // "File not found"
console.log(typeof error.stack); // "string"

Giải thích:

  • name mặc định của Error constructor là "Error".
  • message là argument truyền vào constructor.
  • stack là string được engine tự động gắn vào khi Error được tạo. Format phụ thuộc engine (V8, SpiderMonkey...).

Level 2 — Partial Scaffold (Khung sẵn) ​

Hoàn thành function validateAge để throw đúng built-in error type.

js
function validateAge(age) {
  if (typeof age !== "number") {
    throw new _______("Age must be a number");
  }
  if (age < 0 || age > 150) {
    throw new _______("Age out of valid range");
  }
  return true;
}

// Test:
validateAge("twenty"); // TypeError?
validateAge(200);      // RangeError?
[Đáp án tham khảo]
js
function validateAge(age) {
  if (typeof age !== "number") {
    throw new TypeError("Age must be a number");
  }
  if (age < 0 || age > 150) {
    throw new RangeError("Age out of valid range");
  }
  return true;
}

Giải thích:

  • TypeError: Kiểu dữ liệu sai. typeof age !== "number" nghĩa là người dùng truyền string, null, object...
  • RangeError: Giá trị nằm ngoài phạm vi hợp lý. 200 vẫn là number, nhưng không hợp lệ về mặt domain.

Level 3 — Independent (Tự viết) ​

Viết các function sau:

js
// 1. parseConfig(jsonString) → Dùng JSON.parse. Nếu jsonString invalid, 
//    ném SyntaxError với message "Invalid JSON". Nếu parse ra không phải object,
//    ném TypeError với message "Config must be an object".

// 2. getLength(str) → Nếu str không phải string, ném TypeError.
//    Nếu str rỗng, ném RangeError với message "String cannot be empty".
//    Trả về str.length.

// Sử dụng:
console.log(parseConfig('{"port": 3000}')); // { port: 3000 }
// console.log(parseConfig("not json"));    // SyntaxError: Invalid JSON
// console.log(parseConfig("123"));         // TypeError: Config must be an object

console.log(getLength("hello")); // 5
// console.log(getLength(123));   // TypeError
// console.log(getLength(""));    // RangeError
[Đáp án tham khảo]
js
function parseConfig(jsonString) {
  let parsed;
  try {
    parsed = JSON.parse(jsonString);
  } catch (e) {
    // Giữ nguyên lỗi gốc `e.message` để không mất `stack trace` từ JSON.parse
    throw new SyntaxError("Invalid JSON: " + e.message);
  }
  if (typeof parsed !== "object" || parsed === null) {
    throw new TypeError("Config must be an object");
  }
  return parsed;
}

Hoặc nếu muốn đơn giản hơn:

js
function parseConfig(jsonString) {
  try {
    const parsed = JSON.parse(jsonString);
    if (typeof parsed !== "object" || parsed === null) {
      throw new TypeError("Config must be an object");
    }
    return parsed;
  } catch (e) {
    // Nếu JSON.parse đã throw SyntaxError, giữ nguyên hoặc wrap rõ ràng
    if (e instanceof SyntaxError) {
      throw new SyntaxError("Invalid JSON: " + e.message);
    }
    throw e;
  }
}

Hàm getLength:

js
function getLength(str) {
  if (typeof str !== "string") {
    throw new TypeError("Expected a string");
  }
  if (str.length === 0) {
    throw new RangeError("String cannot be empty");
  }
  return str.length;
}

Giải thích:

  • JSON.parse throw SyntaxError nếu invalid. Ta bắt và throw lại với message rõ ràng hơn.
  • typeof null là "object" nên cần check parsed === null.
  • getLength dùng TypeError cho sai kiểu, RangeError cho giá trị không hợp lệ.

Ở production, việc re-throw new SyntaxError("...") thay thế hoàn toàn lỗi gốc sẽ làm mất stack trace từ JSON.parse.

Cách tốt hơn là wrap message hoặc dùng Error.cause (ES2022, Stage 9). Ở Stage 0, chỉ cần nhận thức: "Không nên âm thầm thay thế lỗi gốc bằng một lỗi mới mà không mang theo thông tin cũ."

9. Edge Cases (Các trường hợp ngoại lệ) ​

SyntaxError parse-time thường không thể catch

js
try {
  eval("const x =");
} catch (e) {
  console.log(e.name); // SyntaxError
}

Nhưng:

js
try {
  // const x =    // Không thể viết cú pháp sai trực tiếp trong file
} catch (e) {}

Tại sao: Nếu file có const x = (thiếu value), engine parse file trước khi chạy và throw SyntaxError ngay. try/catch không bao quát được parse phase. Chỉ eval hoặc Function constructor mới cho phép catch SyntaxError runtime.

Cách nhận biết: SyntaxError trong file thường làm crash toàn bộ script trước khi dòng đầu tiên chạy.

Cách xử lý: Viết cú pháp đúng. SyntaxError không phải lỗi runtime để "xử lý graceful".

throw không bắt buộc phải là Error object

js
throw "Something went wrong";
throw 404;
throw null;

Tại sao: JavaScript cho phép throw bất kỳ value nàu. Nhưng nếu throw primitive, bạn mất stack trace và message chuẩn.

Quan sát sự khác biệt:

js
try {
  throw new Error("With stack");
} catch (e) {
  console.log(typeof e);      // "object"
  console.log(e.stack);       // có stack trace
  console.log(e.message);     // "With stack"
}

try {
  throw "Without stack";
} catch (e) {
  console.log(typeof e);      // "string"
  console.log(e.stack);       // undefined ❌
  console.log(e.message);     // undefined ❌
}

Cách nhận biết: catch (e) nhận được string/number thay vì object có stack. Debugging tool (DevTools, Sentry) không thể hiển thị trace cho primitive.

Cách xử lý: Luôn throw new Error("...") hoặc built-in subtype. Đây là convention để debugging tool hoạt động đúng.

new Error() vs Error()

js
const a = new Error("x");
const b = Error("x");

Tại sao: Cả hai đều tạo Error object trong JS (khác Java). Nhưng convention là dùng new cho clarity.

Cách nhận biết: Không khác biệt behavior.

Cách xử lý: Luôn dùng new để rõ ràng với người đọc.

10. Debug Lab (Bài lab gỡ lỗi) ​

Symptom (Triệu chứng): Developer nhận được lỗi và mất 20 phút sửa sai chỗ.

js
function fetchUser(id) {
  const users = { 1: { name: "Alice" } };
  return users[id];
}

function greetUser(id) {
  const user = fetchUser(id);
  return `Hello, ${user.name}!`;
}

console.log(greetUser(2));
// TypeError: Cannot read properties of undefined (reading 'name')

Reproduction (Tái hiện lỗi): Chạy code với greetUser(2).

Evidence (Bằng chứng):

  • Message: Cannot read properties of undefined (reading 'name')
  • Stack trace (V8):
    text
    at greetUser (app.js:7:25)
    at Object.<anonymous> (app.js:10:13)
  • fetchUser(2) trả về undefined vì users không có key 2.

Hypothesis (Giả thuyết): Developer nhìn TypeError và nghĩ "có gì đó sai kiểu dữ liệu". Họ đổi user.name thành user["name"], thêm String(user.name), rồi kiểm tra typeof user. Nhưng vấn đề không phải kiểu dữ liệu — vấn đề là user là undefined vì ID không tồn tại.

Verification (Xác minh):

js
console.log(fetchUser(2)); // undefined
console.log(typeof fetchUser(2)); // "undefined"

Root Cause (Nguyên nhân gốc rễ): fetchUser trả về undefined khi ID không tồn tại. greetUser giả định user luôn là object và truy cập .name ngay lập tức. TypeError ở đây là triệu chứng của missing data, không phải sai kiểu dữ liệu nội tại.

Fix (Sửa):

js
function greetUser(id) {
  const user = fetchUser(id);
  if (!user) {
    throw new Error(`User ${id} not found`);
  }
  return `Hello, ${user.name}!`;
}

Hoặc defensive hơn:

js
function greetUser(id) {
  const user = fetchUser(id);
  if (typeof user !== "object" || user === null) {
    throw new TypeError("Expected user object");
  }
  return `Hello, ${user.name}!`;
}

Prevention (Phòng ngừa):

  • Đọc message cẩn thận: "Cannot read properties of undefined" nghĩa là giá trị là undefined, không phải "sai type" theo nghĩa class/kind.
  • Luôn hỏi: "Value này có thể là undefined/null không trước khi access property?"
  • Dùng guard clause hoặc early return (0.4.4).

11. Design Exercise (Bài tập thiết kế giải pháp) ​

Bạn đang viết một hàm calculateDiscount(price, rate).

js
function calculateDiscount(price, rate) {
  if (price < 0 || rate < 0 || rate > 1) {
    throw new Error("Invalid input");
  }
  return price * rate;
}
js
function calculateDiscount(price, rate) {
  if (typeof price !== "number" || typeof rate !== "number") {
    throw new TypeError("Price and rate must be numbers");
  }
  if (price < 0) {
    throw new RangeError("Price cannot be negative");
  }
  if (rate < 0 || rate > 1) {
    throw new RangeError("Rate must be between 0 and 1");
  }
  return price * rate;
}

Câu hỏi:

  1. Option A có lợi gì về đơn giản?
  2. Option B có lợi gì khi debug hoặc log lỗi production?
  3. Nếu bạn cần catch lỗi và xử lý khác nhau cho "sai kiểu" và "sai phạm vi", option nào cho phép điều đó?
[Đáp án tham khảo]
  • Bạn nghĩ:

    1. Option A: Ngắn gọn, ít code, dễ viết nhanh.
    2. Option B: error.name cho biết ngay loại vấn đề. Khi log production, bạn có thể filter TypeError khác RangeError. Message cụ thể giúp debug nhanh hơn "Invalid input".
    3. Option B cho phép catch phân biệt:
      js
      try {
        calculateDiscount("100", 0.2);
      } catch (e) {
        if (e instanceof TypeError) { /* handle type issue */ }
        if (e instanceof RangeError) { /* handle range issue */ }
      }
  • Kết luận: Dùng built-in error type đúng semantic. Đừng dùng generic Error khi built-in type đã mô tả chính xác vấn đề. Điều này giúp stack trace "self-documenting".

12. Production Scenario (Tình huống thực tế) ​

Context: Production log từ monitoring tool:

text
TypeError: Cannot read properties of undefined (reading 'map')
    at renderList (components/List.js:14:15)
    at renderDashboard (pages/Dashboard.js:42:10)
    at mountComponent (react-dom.js:...)

Symptom: Trang Dashboard trắng với lỗi này cho một số user.

Constraint: Bạn không thể debug trên máy user. Chỉ có log.

Câu hỏi:

  1. Dựa vào message, giá trị nào là undefined?
  2. Dựa vào stack trace, dòng code nào gây lỗi trực tiếp?
  3. Tại sao lỗi chỉ xảy ra với "một số user" mà không phải tất cả?
[Đáp án tham khảo]
  • Bạn nghĩ:

    1. Một giá trị nào đó trước .map là undefined. Có thể là items hoặc data.list.
    2. renderList tại List.js:14:15. Đây là nơi .map được gọi.
    3. Chỉ một số user có data thiếu field (ví dụ: API trả về null cho items thay vì []). Với user có data đầy đủ, items là array và .map hoạt động.
  • Bài học: Stack trace trong production là dữ liệu quý giá. Message + top frame thường cho bạn 80% thông tin cần thiết. Đừng chỉ nhìn "TypeError" và bối rối — đọc tiếp Cannot read properties of undefined.

13. AI-assisted Exercise (Bài tập với AI) ​

Level B — Challenge

  1. Tự trả lời trước: Dự đoán output:
    js
    const e = new TypeError("fail");
    console.log(e.name);
    console.log(e instanceof Error);
  2. Hỏi AI: "TypeError và Error khác nhau thế nào? TypeError có phải là Error không?"
  3. 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 built-in error types là subtype của Error và kế thừa name/message/stack?
  4. Verify bằng MDN: tìm "Error" trên MDN, đọc phần "Error types".

Gợi ý

AI thường biết TypeError extends Error nhưng đôi khi giải thích mơ hồ bằng cách nói "TypeError là một loại lỗi". Nếu AI không chỉ ra rằng e instanceof Error là true và tại sao điều này quan trọng cho catch (e) generic, bạn đã tìm ra điểm mù. AI cũng có thể nhầm lẫn TypeError với static type checking (TypeScript).

[Đáp án tham khảo]
  • Bạn nghĩ: e.name là "TypeError". e instanceof Error là true vì TypeError kế thừa từ Error. Điều này nghĩa là bạn có thể catch (e) và truy cập e.message dù không biết chính xác subtype.

  • AI trả lời (typical):

    • TypeError is a built-in error type in JavaScript.
    • It is thrown when an operation is performed on a value of an inappropriate type.
    • TypeError is a subclass of Error.
    • Therefore, e instanceof Error returns true.
  • So sánh: AI thường đúng về inheritance nhưng đôi khi không giải thích tại sao điều này quan trọng trong production: bạn có thể viết catch (e) { log(e.message); report(e.stack); } và nó hoạt động cho mọi built-in error type.

  • Điểm AI nói sai hoặc quá mơ hồ: "TypeError is for wrong types" — đúng nhưng chưa đủ. AI hiếm khi nói rõ: "TypeError cũng xảy ra khi access property trên null/undefined, không chỉ khi bạn truyền string thay vì number."

  • Kết luận: Nếu bạn chỉ ra được rằng built-in error types tạo thành một hierarchy và điều này cho phép xử lý lỗi generic, bạn đã hiểu sâu hơn AI. Concept này sẽ quay lại ở Stage 2 (Prototype chain / Class inheritance), Stage 3 (Promise rejection type), và Stage 8 (Error Boundary catch behavior).

14. Teach Back (Dạy lại) ​

Giả sử một junior developer hỏi bạn:

"Em thấy console báo đỏ lòm ReferenceError: user is not defined. Em sợ quá nên refresh trang rồi lại. Em nên làm gì với cái lỗi này?"

Hãy giải thích trong 2 phút, dùng đúng terminology: error object, message, stack trace, error type.

Mô phỏng
  • Bạn nói: Đừng refresh. Đừng sợ. Cái đỏ đỏ đó là thông tin, không phải lỗi hệ thống.

Đầu tiên, đọc error type: ReferenceError. Điều này nghĩa là JavaScript không tìm thấy biến user ở bất kỳ scope nào. Khác với TypeError — nếu là TypeError thì biến có tồn tại nhưng bạn đang dùng sai kiểu.

Thứ hai, đọc message: user is not defined. Nghĩa là bạn dùng user nhưng chưa khai báo const user, let user, hoặc var user. Hoặc bạn khai báo ở scope khác.

Thứ ba, nếu có stack trace, đọc dòng đầu tiên sau message. Nó sẽ chỉ file và dòng code gây lỗi. Ví dụ app.js:24:10 nghĩa là dòng 24, cột 10.

Cách fix: Tìm đến dòng đó, kiểm tra xem user có được khai báo không. Nếu có thể là typo — bạn định viết users nhưng gõ thiếu s.

💡 Tưởng tượng error như một bưu phẩm bị trả lại. Trên bìa ghi rõ: "Người nhận user không tồn tại, gửi từ dòng 24". Bạn không cần Google — bạn cần kiểm tra địa chỉ người nhận.

Gợi ý đánh giá bản thân

  • Đồng nghiệp có hiểu tại sao không nên refresh và bỏ qua lỗi không?
  • Bạn có tránh được việc nói "lỗi đỏ nghĩa là code hỏng" không?
  • Nếu đồng nghiệp hỏi: "Vậy ReferenceError khác TypeError như thế nào?" — bạn trả lời được không? (Gợi ý: ReferenceError = biến không tồn tại. TypeError = thao tác sai trên giá trị đã tồn tại.)
  • Nếu đồng nghiệp hỏi: "Tại sao đôi khi lỗi không có stack trace?" — bạn trả lời được không? (Gợi ý: throw "string" không có stack. Hoặc SyntaxError parse-time.)

15. Assessment (Đánh giá) ​

Objective (Mục tiêu)Hình thức đánh giáTask
Error là objectPrediction (Dự đoán)Prediction Câu 2
Phân biệt error typesPrediction (Dự đoán)Prediction Câu 1, 4, 5
Đọc stack traceDebug LabDebug Lab Section
Tạo Error objectImplementation (Thực hành)Implementation Lab Level 1
Throw đúng built-in typeImplementation (Thực hành)Implementation Lab Level 2–3
Chọn error type đúng semanticDesign ExerciseDesign Exercise Section
Explain error hierarchyTeach BackTeach Back Section

16. Exit Criteria (Tiêu chí qua bài) ​

  • [ ] Có thể giải thích error là object với name, message, stack.
  • [ ] Có thể phân biệt TypeError, ReferenceError, RangeError, SyntaxError, và Error generic.
  • [ ] Có thể dự đoán error type từ một đoạn code lỗi (≥ 4/5 scenarios đúng).
  • [ ] Có thể đọc stack trace cơ bản để xác định function và dòng gây lỗi.
  • [ ] Có thể tạo Error object với message mô tả rõ ràng.
  • [ ] Có thể chọn built-in error type phù hợp cho từng tình huống (type mismatch vs range vs reference).
  • [ ] Có thể debug lỗi TypeError do undefined/null property access và phân biệt với ReferenceError.

17. Spiral Connection (Liên kết xoắn ốc) ​

Previous (Trước): Data Structures (0.6.x) — bạn đã biết object là reference value có property. Error object cũng là object — nó chứa message, stack, name như một object bình thường.

Current (Hiện tại): Errors — Error object, built-in types, message, stack trace. Error là dữ liệu debug, không phải kẻ thù.

Next (Tiếp theo):

  • 0.7.2 (Throw) — Tại sao và khi nào nên chủ động ném error? throw là cơ chế propagation.
  • 0.7.3 (Try/Catch) — Bắt và xử lý error. Stack trace đi kèm với error khi được catch.
  • Stage 1 (Execution Model) — Call stack. Stack trace chính là snapshot của call stack tại thời điểm lỗi xảy ra. Hiểu execution context giúp đọc stack trace chính xác hơn.
  • Stage 3 (Async) — Promise rejection là một dạng async error. UnhandledPromiseRejection.
  • Stage 8 (React) — Error Boundaries. Catch error trong component tree và hiển thị fallback UI.
  • Stage 9/13 (Production) — Error logging, monitoring, incident response. Stack trace là evidence đầu tiên trong production debugging.
📴 Offline Mode — Content served from cache