- Hiểu
Errorobject và các built-in subclass (TypeError,RangeError,SyntaxError,ReferenceError). - Master
try/catch/finally, kể cả khi mix vớiasync/await. - Tự tạo custom error class để phân biệt loại lỗi.
- Biết các anti-pattern phổ biến: swallow error, log không re-throw.
- Hiểu Result type — pattern lấy cảm hứng từ Rust, ép caller phải xử lý lỗi.
- Thiết kế fail-fast, defensive copy, error boundary ở UI.
- Cài global error handler ở browser và Node — lớp phòng cuối.
1. Error object trong JavaScript
JS có một type built-in tên Error. Khi có sự cố runtime (chia cho 0 trên BigInt, gọi method của
undefined, parse JSON sai...), JS tự throw một instance của Error (hoặc subclass).
Developer cũng có thể chủ động throw.
const err = new Error('Something broke');
err.name; // 'Error' — tên loại
err.message; // 'Something broke' — chuỗi cho người đọc
err.stack; // 'Error: Something broke\n at :1:13\n ...' — vị trí throw
// throw nó để dừng chương trình (cho đến khi có catch)
throw err;
1.1. Các subclass built-in
JS có sẵn nhiều subclass của Error. Mỗi cái mô tả một loại lỗi khác nhau.
| Class | Khi nào sinh ra | Ví dụ trigger |
|---|---|---|
Error | Generic — base class | new Error('msg') |
TypeError | Sai type khi vận hành | null.foo, gọi non-function |
RangeError | Giá trị vượt range hợp lệ | new Array(-1), recursion quá sâu |
SyntaxError | Code không parse được | JSON.parse('xx'), eval string sai |
ReferenceError | Truy cập biến chưa khai báo / TDZ | console.log(notDeclared) |
URIError | URI function lỗi | decodeURIComponent('%') |
EvalError | Lịch sử (ít dùng) | — |
AggregateError | Gom nhiều error (Promise.any reject hết) | Promise.any([reject, reject]) |
// TypeError
const x = null;
x.foo; // TypeError: Cannot read properties of null (reading 'foo')
// RangeError
new Array(-1); // RangeError: Invalid array length
// SyntaxError (runtime, từ JSON.parse)
JSON.parse('not json'); // SyntaxError: Unexpected token 'o'
// ReferenceError
console.log(notDeclared); // ReferenceError: notDeclared is not defined
Vì tất cả subclass đều kế thừa Error, bạn có thể check loại lỗi bằng instanceof:
try {
JSON.parse('xx');
} catch (e) {
if (e instanceof SyntaxError) {
console.log('JSON sai cú pháp');
} else if (e instanceof Error) {
console.log('Lỗi khác:', e.message);
}
}
1.2. Anti-pattern: throw 'string'
JavaScript cho phép throw bất kỳ value gì — string, number, object thường. Không nên.
Khi throw string, bạn mất stack trace, e.message, instanceof Error.
throw 'Something broke'; // ❌ không có stack, không có name
throw 404; // ❌ developer phải đoán nghĩa
throw { code: 404 }; // ❌ không là Error → instanceof fail
// ✅ Đúng:
throw new Error('Something broke');
Quy tắc: luôn throw một instance của Error (hoặc subclass). Tự đặt rule này trong
ESLint với no-throw-literal.
2. try / catch / finally
Cấu trúc cơ bản để bắt lỗi synchronous. Khi code trong try throw, control nhảy ngay sang
catch. finally chạy luôn luôn — bất kể có lỗi hay không, kể cả khi
catch re-throw.
function divide(a, b) {
if (b === 0) throw new Error('Division by zero');
return a / b;
}
try {
const r = divide(10, 0);
console.log('kết quả:', r); // không chạy
} catch (e) {
console.error('lỗi:', e.message); // 'lỗi: Division by zero'
} finally {
console.log('luôn chạy'); // chạy
}
2.1. finally chạy khi nào
Ngắn gọn: mọi lúc. Kể cả khi try hoặc catch có return hoặc re-throw.
function readFile(path) {
const file = openFile(path);
try {
return file.readAll(); // có return — finally vẫn chạy
} catch (e) {
throw new Error(`Read failed: ${e.message}`); // re-throw — finally vẫn chạy
} finally {
file.close(); // ✅ luôn đóng file
}
}
Trong C++ có RAII: object tự dọn resource khi out of scope (destructor). JS không có destructor, nhưng
finally là cách thủ công: "resource mở trong try, dọn trong finally". Áp dụng cho
file handle, DB connection, lock, timer, event listener.
2.2. catch không cần binding (ES2019)
Trước ES2019, bạn phải viết catch (e) dù không dùng e. ES2019 cho phép bỏ luôn:
// Cũ:
try { JSON.parse(input); }
catch (e) { return null; } // `e` không dùng nhưng vẫn phải khai báo
// ES2019:
try { JSON.parse(input); }
catch { return null; } // ✅ không cần (e)
3. Custom Error class — phân biệt loại lỗi
Trong code thật, bạn cần phân biệt: lỗi validation (user gõ sai), lỗi network (API timeout), lỗi authorization (token expired)... Mỗi loại có cách xử lý khác. Cách clean nhất là tạo subclass của Error.
class ValidationError extends Error {
constructor(field, message) {
super(message); // gọi Error constructor — set this.message
this.name = 'ValidationError'; // override để log đẹp + instanceof
this.field = field; // thêm thông tin nghiệp vụ
}
}
class NotFoundError extends Error {
constructor(resource) {
super(`${resource} not found`);
this.name = 'NotFoundError';
this.resource = resource;
}
}
// Sử dụng
function getUser(id) {
if (typeof id !== 'number') {
throw new ValidationError('id', 'id phải là number');
}
const u = db.find(id);
if (!u) throw new NotFoundError('User');
return u;
}
// Caller dispatch theo loại
try {
getUser('abc');
} catch (e) {
if (e instanceof ValidationError) {
ui.showFieldError(e.field, e.message);
} else if (e instanceof NotFoundError) {
ui.show404();
} else {
ui.showGenericError();
reportToSentry(e);
}
}
this.name
Mặc định this.name kế thừa từ Error nên sẽ là 'Error', không phải tên
class mới. Khi log err.toString() sẽ ra "Error: msg" thay vì "ValidationError: msg".
Luôn override this.name = 'TenClass' trong constructor.
4. try/catch với async/await
Đây là điểm hay nhất của async/await so với .then().catch(): bạn dùng được
cùng một cấu trúc try/catch cho cả sync và async.
async function loadUser(id) {
try {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return await res.json();
} catch (e) {
console.error('loadUser failed:', e);
throw e; // re-throw cho caller cao hơn
}
}
So sánh với .then().catch():
// Promise chain
function loadUserChain(id) {
return fetch(`/api/users/${id}`)
.then(res => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
})
.catch(e => {
console.error('loadUser failed:', e);
throw e;
});
}
Trong block try { await foo(); }, catch sẽ bắt:
- Lỗi sync ném ra trước khi Promise được tạo (vd:
foosai signature). - Promise reject trả về từ
await foo().
Đó là lý do nó rất gọn. Nhưng nhớ: try/catch không bắt được error xảy ra trong
callback setTimeout, setInterval, hoặc Promise nào không có await.
async function bad() {
try {
foo(); // ❌ quên await — Promise reject không bị bắt
} catch (e) {
console.log('never reaches here');
}
}
async function good() {
try {
await foo(); // ✅
} catch (e) {
console.log('caught', e);
}
}
5. Anti-pattern: nuốt lỗi (swallow)
Đây là tội lỗi số 1 trong code production. try/catch không có nội dung — error
biến mất, bug ẩn đi mãi mãi.
// ❌ Tệ — không ai biết có lỗi xảy ra
try {
const data = JSON.parse(input);
save(data);
} catch {}
// ❌ Cũng tệ — log nhưng không re-throw, UI thinks success
async function submit() {
try {
await api.save(data);
} catch (e) {
console.log(e); // log xong... rồi sao?
}
return { ok: true }; // UI hiện "lưu thành công" dù lỗi 🔥
}
Bug này cực kỳ phổ biến. User báo "tôi bấm save mà data không lưu" — bạn không thấy gì trong log production (vì console.log không gửi đi đâu). Hàng giờ debug.
Quy tắc: nếu bạn catch, bạn phải làm ít nhất 1 trong 3 việc sau:
- Re-throw — không xử lý được, để caller xử lý:
throw e; - Recover — có fallback rõ ràng:
return defaultValue; - Báo cáo error state — return
{ok: false, error: e}để caller biết.
// ✅ Cách 1: re-throw
try { risky(); }
catch (e) {
logger.error('risky failed', e);
throw e;
}
// ✅ Cách 2: recover với default
function safeParse(s) {
try { return JSON.parse(s); }
catch { return {}; } // chấp nhận parse fail → object rỗng
}
// ✅ Cách 3: return error state (Result type)
async function submit() {
try {
await api.save(data);
return { ok: true };
} catch (e) {
return { ok: false, error: e };
}
}
6. Result type pattern — học từ Rust
Rust không có exception. Mọi hàm có thể fail đều trả về Result<T, E> — kiểu union giữa
thành công + value và thất bại + error. Lợi ích: compiler ép caller xử lý.
JS không có compiler ép, nhưng pattern vẫn rất giá trị: caller thấy hàm có thể fail.
// Type: { ok: true, value: T } | { ok: false, error: E }
function ok(value) { return { ok: true, value }; }
function err(error) { return { ok: false, error }; }
function parseAge(input) {
const n = Number(input);
if (Number.isNaN(n)) return err('not a number');
if (n < 0) return err('negative');
if (n > 150) return err('too large');
return ok(n);
}
// Caller buộc phải check .ok trước khi truy cập value/error
const r = parseAge(input);
if (r.ok) {
console.log('Tuổi:', r.value);
} else {
console.log('Lỗi:', r.error);
}
- Throw khi lỗi là exceptional — bất thường, ngoài dự tính. Vd: out of memory, bug logic, DB chết.
- Result khi lỗi là expected — nằm trong "happy path" mở rộng. Vd: validate user input, parse, lookup không tìm thấy.
Rule of thumb: nếu bạn biết trước sẽ có case fail (parse JSON từ user), dùng Result. Nếu fail là "không nên xảy ra", throw.
7. Fail-fast pattern
Triết lý: chết ngay từ đầu hàm còn hơn để code chạy nửa chừng rồi mới phát hiện. Validate input ở dòng đầu, throw nếu sai. Tránh state nửa-vời.
// ❌ Lazy — check muộn, có thể đã ghi DB rồi mới phát hiện
async function createOrder(items, userId) {
await db.orders.insert({ items, userId });
await billing.charge(userId, sum(items)); // quá muộn nếu items rỗng
}
// ✅ Fail-fast — validate trước, chỉ ghi khi chắc
async function createOrder(items, userId) {
if (!Array.isArray(items) || items.length === 0) {
throw new ValidationError('items', 'phải có ít nhất 1 sản phẩm');
}
if (typeof userId !== 'number') {
throw new ValidationError('userId', 'phải là number');
}
await db.orders.insert({ items, userId });
await billing.charge(userId, sum(items));
}
- Half-done state là kẻ thù. Order đã insert, nhưng charge fail — DB dirty.
- Stack trace gần root cause. Throw sớm = log ngay nơi lỗi thực sự.
- Code reader dễ hiểu. Đọc 3 dòng đầu hàm là biết preconditions.
8. Defensive copy
Khi hàm nhận object/array từ caller, đừng mutate nó trực tiếp. Caller có thể không biết bạn sửa, dẫn đến bug "sao state ở chỗ khác cũng đổi?". Pattern: clone trước khi mutate.
// ❌ Mutate input — surprise bug
function addTimestamp(obj) {
obj.timestamp = Date.now();
return obj;
}
const user = { name: 'An' };
addTimestamp(user);
console.log(user); // { name: 'An', timestamp: ... } 🔥 caller bị mutate
// ✅ Defensive copy — clone trước
function addTimestamp(obj) {
return { ...obj, timestamp: Date.now() };
}
// Cho object lồng nhau, dùng structuredClone (hiện đại) hoặc deepClone lib
function deepUpdate(obj, path, value) {
const clone = structuredClone(obj);
_.set(clone, path, value);
return clone;
}
Defensive copy là nền của immutable data — triết lý "không bao giờ sửa, luôn tạo bản mới". Đây là cách React, Redux, và các state library hoạt động. Sẽ học sâu ở chương sau và Pillar OOP/FP.
9. Global error handler — lớp phòng cuối
Dù bạn try/catch kỹ, vẫn có error lọt — uncaught exception, unhandled promise rejection. Khi đó
program có thể crash. Bạn cần một global handler để log + cứu vãn graceful.
9.1. Browser
// Bắt sync error không catch
window.addEventListener('error', (event) => {
console.error('Uncaught error:', event.error);
reportToServer({
type: 'error',
message: event.message,
filename: event.filename,
lineno: event.lineno,
stack: event.error?.stack,
});
});
// Bắt promise reject không catch
window.addEventListener('unhandledrejection', (event) => {
console.error('Unhandled rejection:', event.reason);
reportToServer({
type: 'unhandledrejection',
reason: String(event.reason),
stack: event.reason?.stack,
});
});
9.2. Node.js
process.on('uncaughtException', (err) => {
logger.fatal({ err }, 'Uncaught exception');
// Process ở state không chắc chắn — gracefully exit
process.exit(1);
});
process.on('unhandledRejection', (reason) => {
logger.error({ reason }, 'Unhandled promise rejection');
// Từ Node 15+: mặc định cũng exit. Có thể override.
});
Đừng dùng global handler thay cho try/catch. Mục đích của nó: log để bạn biết
có bug nào lọt, sau đó graceful shutdown (Node) hoặc show fallback UI
(browser). Không phải để "tiếp tục như bình thường" sau khi crash — state có thể đã hỏng.
10. Error boundary trong UI framework
Trong UI declarative (React, Vue, Flutter), một component lỗi có thể làm crash cả cây render. Framework cung cấp error boundary — một component "khoanh vùng" lỗi, fallback UI thay vì trắng màn hình.
class ErrorBoundary extends React.Component {
state = { hasError: false, error: null };
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, info) {
reportToSentry(error, { extra: info });
}
render() {
if (this.state.hasError) {
return <FallbackUI error={this.state.error} />;
}
return this.props.children;
}
}
// Bọc khu vực rủi ro:
<ErrorBoundary>
<UserDashboard />
</ErrorBoundary>
Flutter có khái niệm tương đương: ErrorWidget.builder để override widget hiển thị khi build lỗi.
Sẽ học sâu ở Flutter Chương 6 (Widget) và Chương 10 (Error handling).
Đừng đặt 1 ErrorBoundary ở root rồi xong. Đặt nhiều boundary nhỏ ở các vùng độc lập (sidebar, content, footer). Khi widget X lỗi, chỉ vùng X hỏng — phần còn lại app vẫn dùng được. Đây là nguyên tắc blast radius containment.
11. Log error đúng cách
Lỗi phổ biến: console.log(JSON.stringify(err)) → ra '{}' vì Error có
property name, message, stack là non-enumerable.
JSON.stringify bỏ qua chúng.
const err = new Error('oops');
// ❌ Tệ — mất hết info
JSON.stringify(err); // '{}' 🔥
// ✅ Đúng — extract field tay
JSON.stringify({
name: err.name,
message: err.message,
stack: err.stack,
});
// ✅ Hoặc dùng helper:
function serializeError(err) {
if (!(err instanceof Error)) return { value: String(err) };
return {
name: err.name,
message: err.message,
stack: err.stack,
...err, // spread bao luôn custom properties
};
}
// ✅ Console.log nhận Error object trực tiếp — devtools format đẹp
console.error(err); // hiển thị stack + clickable line
Đừng tự build error reporting. Dùng Sentry, DataDog RUM, hoặc Bugsnag — chúng tự capture stack, breadcrumb (action user trước khi crash), source map (de-minify), release version. Cài 5 phút, tiết kiệm 5 tuần debug.
12. Tư duy phòng thủ tổng thể
Tổng hợp 4 nguyên tắc của chương:
| Nguyên tắc | Vùng áp dụng | Pattern cụ thể |
|---|---|---|
| Fail fast | Đầu hàm, đầu request | Validate input → throw ValidationError |
| Result over throw cho expected error | Parse, validate, lookup | Return {ok, value/error} |
| Defensive copy | Hàm public, library API | Spread ..., structuredClone |
| Blast radius | UI tree, request handler | ErrorBoundary, middleware |
| Last-resort logging | Toàn app | Global handler + Sentry |
Bài tập
Bài 1 — ApiError với metadata
Viết class ApiError extends Error có thêm statusCode (number) và
endpoint (string). Sau đó viết function callApi(url) dùng
fetch, throw ApiError khi response không 2xx.
Đáp án mẫu
class ApiError extends Error {
constructor(statusCode, endpoint, message) {
super(message ?? `API ${endpoint} failed with ${statusCode}`);
this.name = 'ApiError';
this.statusCode = statusCode;
this.endpoint = endpoint;
}
}
async function callApi(url) {
const res = await fetch(url);
if (!res.ok) {
throw new ApiError(res.status, url);
}
return await res.json();
}
// Sử dụng
try {
const data = await callApi('/api/users/999');
} catch (e) {
if (e instanceof ApiError && e.statusCode === 404) {
ui.show404();
} else {
throw e;
}
}
Bài 2 — safeFetch trả Result type
Viết safeFetch(url) trả {ok: true, value: data} khi thành công, {ok: false, error} khi fail.
Cover 3 case: network error, non-2xx, JSON parse fail. Không bao giờ throw.
Đáp án mẫu
async function safeFetch(url) {
let res;
try {
res = await fetch(url);
} catch (e) {
return { ok: false, error: { kind: 'network', cause: e } };
}
if (!res.ok) {
return { ok: false, error: { kind: 'http', status: res.status } };
}
try {
const data = await res.json();
return { ok: true, value: data };
} catch (e) {
return { ok: false, error: { kind: 'parse', cause: e } };
}
}
// Caller
const r = await safeFetch('/api/users');
if (r.ok) {
render(r.value);
} else switch (r.error.kind) {
case 'network': ui.showOffline(); break;
case 'http': ui.showHttpError(r.error.status); break;
case 'parse': ui.showServerError(); break;
}
Lưu ý: error kind là string literal → ở TypeScript sẽ dùng discriminated union (chương 9).
Bài 3 — withRetry có điều kiện
Viết withRetry(fn, retries, shouldRetry):
fn: async function không tham số.retries: số lần retry tối đa.shouldRetry(error): predicate quyết định có retry không (vd: chỉ retry network error, không retry 4xx).
Nếu vượt số retry, throw error cuối cùng.
Đáp án mẫu
async function withRetry(fn, retries, shouldRetry) {
let lastError;
for (let attempt = 0; attempt <= retries; attempt++) {
try {
return await fn();
} catch (e) {
lastError = e;
if (attempt === retries || !shouldRetry(e)) throw e;
// Backoff đơn giản: 100ms, 200ms, 400ms...
await new Promise(r => setTimeout(r, 100 * 2 ** attempt));
}
}
throw lastError;
}
// Sử dụng — chỉ retry khi network hoặc 5xx
const data = await withRetry(
() => callApi('/api/data'),
3,
(e) => e instanceof ApiError ? e.statusCode >= 500 : true
);
Bài 4 — Tìm bug nuốt lỗi
Code dưới có 1 bug khiến lỗi từ saveUser bị nuốt. Tìm và sửa.
async function register(form) {
try {
validate(form);
saveUser(form); // hàm async
return { ok: true };
} catch (e) {
return { ok: false, error: e.message };
}
}
Đáp án
Bug: thiếu await trước saveUser(form). Function async return Promise — nếu
không await, Promise reject ở ngoài try/catch, trở thành unhandled rejection.
register sẽ return {ok: true} dù save fail.
async function register(form) {
try {
validate(form);
await saveUser(form); // ✅ thêm await
return { ok: true };
} catch (e) {
return { ok: false, error: e.message };
}
}
ESLint rule @typescript-eslint/no-floating-promises hoặc require-await
sẽ catch bug này tự động.
Bài 5 — Global error logger vào localStorage
Setup 2 listener window.addEventListener('error') và 'unhandledrejection'.
Mỗi error append vào array trong localStorage key 'errorLog' (max 50 entry,
xoá entry cũ nhất khi đầy). Mỗi entry gồm: timestamp, type, message,
stack.
Test: trigger 1 sync error (null.foo) và 1 async error (Promise.reject('x')), kiểm tra log.
Đáp án mẫu
const MAX_LOG = 50;
const LOG_KEY = 'errorLog';
function appendLog(entry) {
let log = [];
try {
log = JSON.parse(localStorage.getItem(LOG_KEY) ?? '[]');
} catch { log = []; }
log.push(entry);
if (log.length > MAX_LOG) log = log.slice(-MAX_LOG);
localStorage.setItem(LOG_KEY, JSON.stringify(log));
}
window.addEventListener('error', (event) => {
appendLog({
timestamp: new Date().toISOString(),
type: 'error',
message: event.message,
stack: event.error?.stack ?? null,
});
});
window.addEventListener('unhandledrejection', (event) => {
const reason = event.reason;
appendLog({
timestamp: new Date().toISOString(),
type: 'unhandledrejection',
message: reason instanceof Error ? reason.message : String(reason),
stack: reason?.stack ?? null,
});
});
// Trigger để test:
setTimeout(() => { null.foo; }, 100); // sync
Promise.reject(new Error('async fail')); // async
// Kiểm tra:
console.log(JSON.parse(localStorage.getItem(LOG_KEY)));
Lưu ý: localStorage dung lượng giới hạn (~5MB) — nhớ cap số entry. Production nên gửi
log lên server (Sentry / endpoint riêng) thay vì lưu local.
Quiz
throw 'oops' khác throw new Error('oops') ở chỗ nào?
Xem đáp án
throw 'oops' ném string trần — không có stack trace, không có
.message, instanceof Error trả false. Catcher khó debug và khó dispatch theo
loại. Luôn dùng new Error(...) (hoặc subclass) — ESLint rule no-throw-literal
bắt vi phạm tự động.
try { await foo() } catch(e) { ... } — catch sẽ bắt loại lỗi nào?
Xem đáp án
Cả hai: (1) lỗi sync throw trước khi Promise được tạo (vd: gọi foo trong
điều kiện sai), và (2) Promise reject trả về từ await foo().
Đây là điểm hay nhất của async/await — bạn dùng cùng một cấu trúc cho cả sync và async.
Không bắt được: error trong callback của setTimeout/event listener, hoặc Promise quên
await.
finally có chạy nếu catch re-throw không?
Xem đáp án
Có. finally chạy luôn luôn — bất kể try success, catch
được kích hoạt, catch re-throw, hay có return sớm. Đây là đặc tính cốt lõi:
finally đảm bảo cleanup (đóng file, release lock) chạy trong mọi trường hợp.
Trong try { risky(); } catch(e) { logger.log(e); } — error có propagate lên hàm gọi risky không?
Xem đáp án
Không. catch đã "tiêu thụ" error — control flow tiếp tục bình thường sau block. Để propagate,
phải throw e; (hoặc throw error mới) trong catch. Đây là anti-pattern phổ biến:
log nhưng không re-throw → caller tưởng thành công.
JSON.parse('xx') throw cái gì, và instanceof Error có trả true không?
Xem đáp án
Throw một SyntaxError. Vì SyntaxError kế thừa Error, cả hai check
đều true:
try { JSON.parse('xx'); }
catch (e) {
e instanceof SyntaxError; // true
e instanceof Error; // true (kế thừa)
}
Đó là lý do catch (e) { if (e instanceof Error) ... } rất an toàn cho mọi error built-in.
Khi nào nên throw error, khi nào nên return error?
Xem đáp án
Throw cho lỗi exceptional — bất ngờ, ngoài happy path, không nên xảy ra
(out-of-memory, bug logic, network down, DB chết).
Return Result ({ok, value/error}) cho lỗi expected — nằm trong
kịch bản dự kiến, caller chắc chắn phải xử lý (parse user input, validate form, lookup không tìm thấy).
Pattern Result lấy cảm hứng từ Rust, ép caller check trước khi truy cập value.
Promise.reject(new Error('x')) không có .catch() đi kèm — chuyện gì xảy ra?
Xem đáp án
Trigger event 'unhandledrejection':
- Browser:
window.addEventListener('unhandledrejection', ...)nhận event. Mặc định console log warning, app tiếp tục chạy. - Node.js:
process.on('unhandledRejection', ...)nhận event. Từ Node 15+ process exit với mã 1 (mặc định) nếu không handler — coi như crash.
Luôn handle Promise reject: .catch(), try/catch với await, hoặc
đảm bảo có global handler. Trong test, unhandled rejection thường làm test fail.
Tổng kết
Sau chương 7, bạn nên master:
- Error object: cấu trúc
name/message/stack, các subclass built-in, luôn throw instance củaError. - try/catch/finally: cú pháp đầy đủ,
finallyluôn chạy,catchkhông cần binding (ES2019). - Custom error class: extends Error, set
this.name, thêm field nghiệp vụ — dispatch bằnginstanceof. - async/await + try/catch: bắt cả sync throw và Promise reject. Cẩn thận quên
await. - Anti-pattern: nuốt lỗi (catch rỗng), log không re-throw. Nếu catch, phải re-throw / recover / báo error state.
- Result type: pattern Rust — return
{ok, value/error}cho expected error. - Fail-fast + defensive copy: validate đầu hàm, clone object trước khi mutate.
- Global handler + Error boundary: lớp phòng cuối ở browser/Node và UI framework.
- Log đúng cách: không JSON.stringify Error (mất stack), dùng Sentry ở production.
Kết nối
- Chương 6 (Async) — error trong Promise chain (
.catch) liên kết trực tiếp với try/catch async ở đây. - Chương 8 (Modules) — module boundary là nơi tự nhiên đặt error policy: validate input, expose typed error.
- Chương 9 (TypeScript) — Result type lên một level với discriminated union + exhaustive check. Error kind trở thành type literal compiler ép xử lý đủ case.
- Dart Chương 5 — exception trong Dart có syntax tương tự (
try/on/catch/finally), thêmrethrowkeyword. Sound null safety giảm hẳnTypeError. - Flutter Chương 6 (Widgets) —
ErrorWidget.builderđể override khi build error. - Flutter Chương 10 (Error handling) —
FlutterError.onError,PlatformDispatcher.instance.onError, integrate Sentry/Crashlytics.