Chuỗi các hứa hẹn thật tuyệt vời khi xử lý lỗi. Khi một lời hứa từ chối, điều khiển nhảy đến trình xử lý từ chối gần nhất. Điều đó rất thuận tiện trong thực tế.

Chẳng hạn, trong đoạn code bên dưới URL fetchbị sai (không có trang nào như vậy) và .catchxử lý lỗi:

fetch('https://no-such-server.blabla') // rejects
  .then(response => response.json())
  .catch(err => alert(err)) // TypeError: failed to fetch (the text may vary)

Như bạn có thể thấy, .catchkhông nhất thiết phải ngay lập tức. Nó có thể xuất hiện sau một hoặc có thể một vài .then.

Hoặc, có thể, mọi thứ đều ổn với trang web, nhưng phản hồi không phải là JSON hợp lệ. Cách dễ nhất để bắt tất cả các lỗi là nối .catchvào cuối của chuỗi:

/*
Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
@author cafedevn
Contact: cafedevn@gmail.com
Fanpage: https://www.facebook.com/cafedevn
Instagram: https://instagram.com/cafedevn
Twitter: https://twitter.com/CafedeVn
Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
*/

fetch('/article/promise-chaining/user.json')
  .then(response => response.json())
  .then(user => fetch(`https://api.github.com/users/${user.name}`))
  .then(response => response.json())
  .then(githubUser => new Promise((resolve, reject) => {
    let img = document.createElement('img');
    img.src = githubUser.avatar_url;
    img.className = "promise-avatar-example";
    document.body.append(img);

    setTimeout(() => {
      img.remove();
      resolve(githubUser);
    }, 3000);
  }))
  .catch(error => alert(error.message));

Thông thường, như vậy .catchkhông kích hoạt ở tất cả. Nhưng nếu bất kỳ lời hứa nào ở trên từ chối (một sự cố mạng hoặc json không hợp lệ hoặc bất cứ điều gì), thì nó sẽ bắt được nó.

1. try…catch

Code của một người thực hiện lời hứa và người xử lý lời hứa có một “vô hình try..catch” xung quanh nó. Nếu một ngoại lệ xảy ra, nó sẽ bị bắt và bị coi là một sự từ chối.

Ví dụ, code này:


new Promise((resolve, reject) => {
  throw new Error("Whoops!");
}).catch(alert); // Error: Whoops!

Các công trình chính xác giống như thế này:

new Promise((resolve, reject) => {
  reject(new Error("Whoops!"));
}).catch(alert); // Error: Whoops!

“Vô hình try..catch” xung quanh người thi hành sẽ tự động bắt lỗi và biến nó thành lời hứa bị từ chối.

Điều này xảy ra không chỉ trong hàm thực thi, mà cả trong các trình xử lý của nó. Nếu chúng ta throwbên trong một trình xử lý.then, điều đó có nghĩa là một lời hứa bị từ chối, vì vậy điều khiển sẽ chuyển sang trình xử lý lỗi gần nhất.

Đây là một ví dụ:

new Promise((resolve, reject) => {
  resolve("ok");
}).then((result) => {
  throw new Error("Whoops!"); // rejects the promise
}).catch(alert); // Error: Whoops!

Điều này xảy ra cho tất cả các lỗi, không chỉ những lỗi gây ra bởi câu lệnh throw. Ví dụ: lỗi lập trình:

new Promise((resolve, reject) => {
  resolve("ok");
}).then((result) => {
  blabla(); // no such function
}).catch(alert); // ReferenceError: blabla is not defined

.catchkhông chỉ bắt được sự từ chối rõ ràng, mà còn có lỗi vô ý trong các trình xử lý ở trên.

2. Rethrowing

Như chúng ta đã nhận thấy, .catchở cuối chuỗi tương tự try..catch. Chúng ta có thể có nhiều .thentrình xử lý như chúng ta muốn, và sau đó sử dụng một lệnh .catchở cuối để xử lý lỗi trong tất cả chúng.

Thông thường, try..catchchúng ta có thể phân tích lỗi và có thể suy nghĩ lại nếu không thể xử lý được. Điều tương tự là có thể cho những lời hứa.

Nếu chúng ta throwở bên trong .catch, thì điều khiển sẽ chuyển đến bộ xử lý lỗi gần nhất tiếp theo. Và nếu chúng ta xử lý lỗi và kết thúc bình thường, thì nó sẽ tiếp tục xử lý .then thành công gần nhất tiếp theo .

Trong ví dụ dưới đây, .catchxử lý lỗi thành công:

// the execution: catch -> then
new Promise((resolve, reject) => {

  throw new Error("Whoops!");

}).catch(function(error) {

  alert("The error is handled, continue normally");

}).then(() => alert("Next successful handler runs"));

Ở đây khối .catch kết thúc bình thường. Vì vậy, xử lý.then thành công tiếp theo được gọi.

Trong ví dụ dưới đây, chúng ta thấy tình huống khác với .catch. Trình xử lý (*)bắt lỗi và chỉ không thể xử lý nó (ví dụ: nó chỉ biết cách xử lý URIError), vì vậy nó sẽ ném lại:

/*
Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
@author cafedevn
Contact: cafedevn@gmail.com
Fanpage: https://www.facebook.com/cafedevn
Instagram: https://instagram.com/cafedevn
Twitter: https://twitter.com/CafedeVn
Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
*/

// the execution: catch -> catch -> then
new Promise((resolve, reject) => {

  throw new Error("Whoops!");

}).catch(function(error) { // (*)

  if (error instanceof URIError) {
    // handle it
  } else {
    alert("Can't handle such error");

    throw error; // throwing this or another error jumps to the next catch
  }

}).then(function() {
  /* doesn't run here */
}).catch(error => { // (**)

  alert(`The unknown error has occurred: ${error}`);
  // don't return anything => execution goes the normal way

});

Việc thực hiện nhảy từ cái đầu tiên .catch (*)đến cái tiếp theo (**)xuống chuỗi.

3. Từ chối

Điều gì xảy ra khi một lỗi không được xử lý? Chẳng hạn, chúng ta đã quên nối .catchvào cuối chuỗi, như ở đây:

new Promise(function() {
  noSuchFunction(); // Error here (no such function)
})
  .then(() => {
    // successful promise handlers, one or more
  }); // without .catch at the end!

Trong trường hợp có lỗi, lời hứa sẽ bị từ chối và việc thực thi sẽ chuyển sang xử lý từ chối gần nhất. Nhưng không có. Vì vậy, lỗi bị mắc kẹt nghiêm trọng. Không có code để xử lý nó.

Trong thực tế, giống như với các lỗi chưa được xử lý thông thường trong mã, điều đó có nghĩa là một cái gì đó đã sai lầm khủng khiếp.

Điều gì xảy ra khi một lỗi thông thường xảy ra và không bị bắt bởi try..catch? Kịch bản chết với một thông điệp trong console. Một điều tương tự xảy ra với sự từ chối lời hứa chưa được xử lý.

Công cụ JavaScript theo dõi các từ chối như vậy và tạo ra lỗi toàn cục trong trường hợp đó. Bạn có thể thấy nó trong bảng điều khiển nếu bạn chạy ví dụ trên.

Trong trình duyệt, chúng ta có thể bắt lỗi như vậy bằng cách sử dụng sự kiện unhandledrejection:

window.addEventListener('unhandledrejection', function(event) {
  // the event object has two special properties:
  alert(event.promise); // [object Promise] - the promise that generated the error
  alert(event.reason); // Error: Whoops! - the unhandled error object
});

new Promise(function() {
  throw new Error("Whoops!");
}); // no catch to handle the error

Sự kiện này là một phần của chuẩn HTML .

Nếu xảy ra lỗi và không có .catch, unhandledrejectiontrình xử lý sẽ kích hoạt và lấy đối tượngevent có thông tin về lỗi, vì vậy chúng ta có thể làm gì đó.

Thông thường các lỗi như vậy là không thể phục hồi, vì vậy cách tốt nhất của chúng ta là thông báo cho người dùng về vấn đề và có thể báo cáo sự cố cho máy chủ.

Trong các môi trường không có trình duyệt như Node.js, có nhiều cách khác để theo dõi các lỗi chưa xử lý.

4. Tóm lược

  • .catchxử lý các lỗi trong các loại lời hứa: có thể là một reject()cuộc gọi hoặc một lỗi được đưa vào một trình xử lý.
  • Chúng ta nên đặt .catchchính xác ở những nơi chúng ta muốn xử lý lỗi và biết cách xử lý chúng. Trình xử lý nên phân tích lỗi (trợ giúp các lớp lỗi tùy chỉnh) và chia sẻ lại những lỗi chưa biết (có thể chúng là lỗi lập trình).
  • Không nên sử dụng .catchchút nào, nếu không có cách nào để khắc phục lỗi.
  • Trong mọi trường hợp, chúng ta nên có trình xử lý sự kiệnunhandledrejection (cho trình duyệt và tương tự cho các môi trường khác) để theo dõi các lỗi chưa xử lý và thông báo cho người dùng (và có thể là máy chủ của chúng ta) về chúng, để ứng dụng của chúng ta không bao giờ chết.

Full series tự học Javascript từ cơ bản tới nâng cao tại đây nha.

Nếu bạn thấy hay và hữu ích, bạn có thể tham gia các kênh sau của cafedev để nhận được nhiều hơn nữa:

Chào thân ái và quyết thắng!

Đăng ký kênh youtube để ủng hộ Cafedev nha các bạn, Thanks you!