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

JavaScript return await: vì sao try/catch có thể bỏ lỡ lỗi Promise?

Hàm có try/catch nhưng Promise bị reject vẫn đi thẳng ra nơi gọi. Trong async function, trả về một Promise và chờ nó bên trong try là hai cách đặt ranh giới xử lý lỗi khác nhau. Không nên xóa await chỉ vì thấy nó nằm ngay sau return.

JavaScript return await: vì sao try/catch có thể bỏ lỡ lỗi Promise?

Hàm có try/catch nhưng Promise bị reject vẫn đi thẳng ra nơi gọi. Trong async function, trả về một Promise và chờ nó bên trong try là hai cách đặt ranh giới xử lý lỗi khác nhau. Không nên xóa await chỉ vì thấy nó nằm ngay sau return.

Các ví dụ dưới đây chạy trong async function hoặc môi trường hỗ trợ top-level await. Chúng chỉ dùng Promise giả lập, không gửi request hay đọc dữ liệu production.

1. return Promise không đưa rejection vào catch cục bộ

async function readStatus() {
  try {
    return Promise.reject(new Error('offline'));
  } catch (error) {
    return 'fallback';
  }
}
try {
  await readStatus();
} catch (error) {
  console.log('outer:' + error.message); // outer:offline
}

Việc tạo và trả Promise không ném lỗi đồng bộ trong try. Async function chuyển kết quả của Promise đó tới Promise trả về của nó; nơi gọi đang await sẽ nhận rejection. catch cục bộ không có bước await để biến rejection thành lỗi tại vị trí đó.

2. return await giữ việc chờ trong phạm vi try

async function readStatus() {
  try {
    return await Promise.reject(new Error('offline'));
  } catch (error) {
    console.log('local:' + error.message); // local:offline
    return 'fallback';
  }
}
console.log(await readStatus()); // fallback

await gặp Promise bị reject sẽ ném lý do từ chối tại biểu thức đang chờ, nên catch bao quanh xử lý được. Có thể ghi nhận ngữ cảnh rồi ném lại, hoặc trả phương án dự phòng nếu hợp đồng cho phép.

fallback chỉ là minh họa. Không biến mọi lỗi thành dữ liệu thành công: nơi gọi cần phân biệt kết quả thật, dữ liệu dự phòng và lỗi không thể phục hồi theo nghiệp vụ.

3. Lỗi đồng bộ và rejection phải được thử riêng

async function read(call) {
  try {
    return call();
  } catch (error) {
    return 'fallback';
  }
}
console.log(await read(() => { throw new Error('sync'); })); // fallback
try {
  await read(() => Promise.reject(new Error('async')));
} catch (error) {
  console.log(error.message); // async
}

Test chỉ dùng mock ném đồng bộ có thể khiến bạn tin catch đã bao phủ toàn bộ. Ví dụ cho thấy cùng wrapper nhưng hai kiểu thất bại đi theo hai đường khác nhau. Khi dependency thực tế trả Promise, mock cần có cả trường hợp reject.

4. finally cũng phụ thuộc vị trí chờ

async function observe(waitInside) {
  const events = [];
  async function task() {
    await Promise.resolve();
    events.push('done');
  }
  async function wrapper() {
    try {
      if (waitInside) return await task();
      return task();
    } finally {
      events.push('cleanup');
    }
  }
  await wrapper();
  return events.join(',');
}
console.log(await observe(false)); // cleanup,done
console.log(await observe(true)); // done,cleanup

Ở nhánh trả Promise trực tiếp, finally chạy trước khi task hoàn tất. Nhánh return await chờ task xong rồi mới đi qua finally. Đây là điều cần kiểm tra nếu cleanup giải phóng tài nguyên mà tác vụ vẫn đang dùng.

Ví dụ ghi sự kiện để kiểm chứng thứ tự, không thực sự mở file hoặc kết nối. Cleanup ném lỗi vẫn có thể thay đổi kết quả cuối; cần chính sách xử lý riêng. Bài không đưa ra tuyên bố rằng return await luôn nhanh hơn hay chậm hơn.

5. Kiểm tra ranh giới trước khi refactor

  • Hàm này chịu trách nhiệm bắt lỗi hay chuyển lỗi lên trên?
  • Dependency có thể ném đồng bộ, reject hay cả hai?
  • finally phải chạy khi trả Promise hay khi tác vụ hoàn tất?
  • Fallback có được cho phép và có được nhận diện rõ không?
  • Test có await kết quả để quan sát đúng rejection không?

Tám khối ví dụ Việt–Anh đã chạy tuần tự bằng Node.js, kèm kiểm tra Promise thành công, reject và lỗi đồng bộ. Chưa kiểm thử HTTP, kết nối cơ sở dữ liệu hoặc hiệu năng. Tham khảo MDN await, async function và try...catch.

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.