Lập trình · 23/10/2026

Promise.then: vì sao callback lỗi thứ hai không bắt được lỗi trong callback thành công?

promise.then(onSuccess, onError) không có cùng phạm vi bắt lỗi với promise.then(onSuccess).catch(onError). Nhầm hai cách viết có thể khiến lỗi parse dữ liệu đi qua callback mà lập trình viên tưởng đã xử lý. Cần nhìn vào Promise nào đang được quan sát tại mỗi bước.

Promise.then: vì sao callback lỗi thứ hai không bắt được lỗi trong callback thành công?

promise.then(onSuccess, onError) không có cùng phạm vi bắt lỗi với promise.then(onSuccess).catch(onError). Nhầm hai cách viết có thể khiến lỗi parse dữ liệu đi qua callback mà lập trình viên tưởng đã xử lý. Cần nhìn vào Promise nào đang được quan sát tại mỗi bước.

1. Callback thứ hai chỉ nhận rejection của Promise đầu vào

const events = [];
await Promise.resolve('{bad json}')
  .then(
    text => JSON.parse(text),
    () => { events.push('source failed'); }
  )
  .catch(error => {
    events.push(error instanceof SyntaxError ? 'parse failed' : 'other');
  });
console.log(events.join(', ')); // parse failed

Promise đầu vào đã fulfilled nên callback parse được gọi. Lỗi phát sinh trong callback này làm Promise do then trả về bị rejected; callback thứ hai trong cùng lời gọi then không xử lý rejection mới đó. catch phía sau mới quan sát Promise vừa được tạo.

Đối chiếu MDN Promise.then: then trả về một Promise khác, kết quả của callback quyết định trạng thái tiếp theo. Khi review, vẽ từng mắt xích thay vì xem cả dòng như một khối try/catch duy nhất.

2. Đặt catch sau bước biến đổi để bao quát cả hai nguồn lỗi

async function parsePayload(source) {
  return source
    .then(text => JSON.parse(text))
    .catch(error => {
      throw new Error('Cannot parse payload', { cause: error });
    });
}
for (const source of [
  Promise.resolve('{"count":0}'),
  Promise.resolve('{bad json}')
]) {
  try {
    console.log((await parsePayload(source)).count); // 0
  } catch (error) {
    console.log(error.cause instanceof SyntaxError); // true
  }
}

Helper dành cho Promise chuẩn có giá trị fulfilled là chuỗi JSON. catch này nhận được cả rejection từ source lẫn lỗi parse ở bước trước. Nó bổ sung ngữ cảnh rồi ném lại, vì vậy bên gọi vẫn phải await và xử lý lỗi. cause giữ lại nguyên nhân thay vì thay nó bằng một thông báo chung.

JSON parse thành công chưa đồng nghĩa payload có schema đúng. Ví dụ chỉ dùng object có count để minh họa; ứng dụng cần kiểm tra kiểu, trường bắt buộc và miền giá trị trước khi sử dụng. Cũng đừng gọi mọi lỗi trong chuỗi là lỗi mạng: lỗi nguồn và lỗi biến đổi cần thông tin chẩn đoán riêng.

3. Xử lý lỗi xong có thể vô tình biến thành thành công

const events = [];
const result = await Promise.reject(new Error('offline'))
  .catch(() => { events.push('reported'); });
console.log(result === undefined); // true
console.log(events.join(', ')); // reported

const fallback = await Promise.reject(new Error('offline'))
  .catch(() => ({ count: 0 }));
console.log(fallback.count); // 0

Callback catch chỉ ghi nhận rồi kết thúc sẽ trả undefined, làm Promise tiếp theo fulfilled với giá trị đó. Trả object dự phòng cũng phục hồi thành công, nhưng lần này có dữ liệu rõ ràng. Theo MDN Promise.catch, catch là dạng viết gọn của then(undefined, onRejected).

Chọn fallback khi sản phẩm thực sự cho phép dữ liệu thay thế. Số đếm 0 giả lập có thể khiến người dùng hiểu nhầm “không tải được” thành “không có bản ghi”; giao diện cần trạng thái lỗi hoặc thông tin dữ liệu cũ phù hợp. Nếu chỉ ghi log và cần báo thất bại tiếp, ném lại lỗi hoặc trả một Promise rejected.

4. Phạm vi chuỗi là một phần của thiết kế xử lý lỗi

catch chỉ quan sát những tác vụ được nối vào chuỗi mà nó nhận. Khi callback khởi chạy một tác vụ bất đồng bộ khác, cần return hoặc await tác vụ đó nếu lỗi của nó phải truyền về cùng luồng. Việc chỉ gọi rồi bỏ Promise trả về không tự mở rộng phạm vi catch.

Nếu chủ động dùng hai callback trong then để tách lỗi nguồn khỏi lỗi biến đổi, giữ một nơi xử lý rejection của Promise được trả về. Mục tiêu là phân loại lỗi có chủ đích, không phải đổi mọi then thành catch một cách máy móc.

5. Checklist trước khi sửa chuỗi Promise

  • Callback xử lý lỗi đang quan sát Promise nào?
  • Đã thử cả source reject và callback thành công ném lỗi?
  • Promise của tác vụ con có được return hoặc await?
  • Catch đang phục hồi bằng dữ liệu hay chỉ ghi log rồi trả undefined?
  • Bên gọi còn nhận được nguyên nhân gốc và trạng thái thất bại cần thiết?

Sáu khối mã Việt–Anh đã chạy bằng Node.js. Kiểm tra bổ sung xác minh helper giữ cause của rejection nguồn, nhận lỗi JSON và trả dữ liệu parse hợp lệ. Chưa tích hợp HTTP hoặc giao diện thực tế.

Thảo luận

Bình luận 0

Đăng nhập để bình luận

Bạn cần có tài khoản để tham gia thảo luận và trả lời độc giả khác.

Đăng nhậpĐăng ký

Chưa có bình luận. Hãy là người đầu tiên chia sẻ ý kiến.