Error Handling With Promise Trong JavaScript
Chào các bạn!
Ở bài học trước chúng ta đã cùng nhau tìm hiểu về Promises Chaining
và chắc chắn các bạn có thể thấy được Promise chains
vô cùng hiệu quả trong việc xử lí lỗi. Bài học hôm nay chúng ta cũng sẽ tìm hiểu về các cách xử lí lỗi nữa nhưng bằng các cách khác nhau. Nào bắt đầu thôi!
Như các bạn đã biết Promise chain
rất tốt trong việc xử lý lỗi. Khi một promise
bị từ chối, trình điều khiển sẽ chuyển nó đến trình xử lý từ chối gần nhất trong chuỗi. Điều đó rất thuận tiện trong thực tế. Ví dụ: Trong đoạn code bên dưới, URL là sai (Vì không có địa chỉ trang web nào như vậy) và .catch
sẽ xử 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, .catch
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ệ. Vì vậy cách dễ nhất để bắt tất cả các lỗi đó là thêm .catch
vào cuối chuỗi như sau:
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));
Nếu bất kỳ promises
nào ở trên bị từ chối (do sự cố mạng hoặc json
không hợp lệ hoặc bất cứ điều gì), thì .catch
sẽ bắt được các lỗi đó.
Implicit try...catch
Các đoạn code của chúng ta có thể vô hình có một try..catch
xung quanh để khi có sự cố nào đó xảy ra lỗi đó sẽ bị từ chối. Ví dụ chúng ta có 2 đoạn code:
new Promise((resolve, reject) => {
throw new Error("Whoops!");
}).catch(alert); // Error: Whoops!
Và
new Promise((resolve, reject) => {
reject(new Error("Whoops!"));
}).catch(alert); // Error: Whoops!
Hai đoạn code trên hoạt động giống nhau. Tuy nhiên try..catch
vô hình xung quanh sẽ tự động bắt lỗi và biến nó thành promises
bị từ chối. Điều này xảy ra không chỉ trong hàm thực thi mà còn trong các trình xử lý của nó. Nếu chúng ta throw
bên trong một .then
, điều đó có nghĩa là một promises
bị từ chối, do đó trình điều khiển sẽ chuyển nó đến 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 đối với tất cả các lỗi, không chỉ những lỗi do câu lệnh throw
gây ra . Ví dụ:
new Promise((resolve, reject) => {
resolve("ok");
}).then((result) => {
blabla(); // no such function
}).catch(alert); // ReferenceError: blabla is not defined
.catch
lúc này không chỉ bắt những lời từ chối rõ ràng, mà còn cả những lỗi vô tình do người xử lý ở trên.
Rethrowing
Như chúng ta thấy ở trên, .catch
ở cuối chuỗi tương tự như try..catch
. Chúng ta có thể có nhiều .then
tùy thích và sau đó sử dụng một trình xử lý duy nhất .catch
ở cuối để xử lý lỗi trong tất cả chúng. Thông thường, với try..catch
chúng ta có thể phân tích lỗi và có thể rethrow
nó nếu không thể xử lý được. Điều tương tự cũng có thể xảy ra đối với promises
.
Nếu chúng ta throw
bên trong .catch
, thì trình điều khiển sẽ chuyển nó đến trình 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 đến .then
gần nhất. Trong ví dụ bên dưới, .catch
xử 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 .catch
được kết thúc bình thường. Vì vậy, trình xử lý .then
thành công tiếp theo được gọi. Trong ví dụ tiếp theo, chúng ta thấy tình huống khác với .catch
. Trình xử lý (*)
bắt lỗi và không thể xử lý nó (ví dụ: nó chỉ biết cách xử lý URIError
):
// the execution: catch -> catch
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
});
Quá trình thực hiện sẽ nhảy từ bước đầu tiên .catch
(*)
sang bước tiếp theo trong chuỗi (**)
.
Unhandled rejections
Vậy điều gì xảy ra khi một lỗi không được xử lý? Ví dụ: chúng ta đã quên thêm .catch
vào cuối chuỗi như sau:
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, promises
sẽ bị từ chối và việc thực thi sẽ chuyển đến trình xử lý từ chối gần nhất. Nhưng không có, vì vậy, lỗi bị "kẹt". Điều gì xảy ra khi một lỗi thường xuyên xảy ra và không bị bắt bởi try..catch
? Script
sẽ chết với một thông báo ở console
. Một điều tương tự cũng xảy ra với việc không xử lí các promises
bị từ chối. Vì vậy mà công cụ JavaScript
giúp ta theo dõi những từ chối như vậy. Bạn có thể thấy nó ở console
nếu bạn chạy ví dụ trên.
Trong trình duyệt, chúng ta cũng có thể gặp các lỗi như vậy bằng cách sử dụng 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
Nếu có lỗi xảy ra và không có .catch
, unhandledrejection
sẽ kích hoạt và lấy event
của đối tượng và thông tin về lỗi, vì vậy chúng ta có thể khắc phục nó thông qua các thông báo về lỗi. Tuy nhiên thông thường những lỗi như vậy không thể khôi phục được, 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ề sự cố và có thể báo cáo sự cố cho máy chủ.
Tổng kết
Vậy là bài học thứ 4 trong series tìm hiểu về promises, async/await
của chúng ta đã khép lại. Hẹn gặp lại các bạn trong bài học tiếp theo về Promises API. Còn bây giờ chúng ta sẽ điểm lại những nét chính trong bài học ngày hôm nay:
.catch
có thể xử lý lỗi trong hầu hết cácpromises
: có thể là mộtreject()
hay một lỗi được trong trình xử lý.- Chúng ta nên đặt
.catch
chính xác ở những nơi mà chúng ta muốn xử lý lỗi và biết cách xử lý. Trình xử lý nên phân tích các lỗi và nên loại bỏ các đến từ quá trình lập trình. - Trong mọi trường hợp, chúng ta nên có
unhandledrejection
trình xử lý sự kiện (đối với trình duyệt và tương tự đối với các môi trường khác) để theo dõi các lỗi chưa được khắc phục và thông báo cho người dùng (và có thể là máy chủ) về chúng, để ứng dụng của chúng ta không bao giờ “chết”.