[Tự học Javascript] Xử lý lỗi, “try..catch” trong Javascript » https://final-blade.com

Cho dù tất cả chúng ta có giỏi lập trình đến đâu, nhiều lúc những tập lệnh của tất cả chúng ta cũng có lỗi. Chúng hoàn toàn có thể xảy ra do lỗi của tất cả chúng ta, nguồn vào của người dùng không mong ước, phản hồi của sever bị lỗi và vì hàng ngàn nguyên do khác .

Thông thường, một đoạn script Chết (ngay lập tức dừng lại) trong trường hợp có lỗi, in nó ra console.

Nhưng có một cấu trúc cú pháp try..catchcho phép chúng ta bắt lỗi các lỗi, vì vậy tập lệnh có thể, thay vì chết, làm điều gì đó hợp lý hơn.

1. Cú pháp của “try…catch”

Cấu trúctry..catch có hai khối chính: tryvà sau đó catch:

try {

  // code...

} catch (err) {

  // error handling

}

Nó hoạt động giải trí như thế này :

  1. Đầu tiên, code trong try {...}được thực thi.
  2. Nếu không có lỗi, thì vào catch(err), nếu khôn lỗi thì được bỏ qua: việc thực thi đến hết tryvà tiếp tục, bỏ qua catch.
  3. Nếu xảy ra lỗi, thì việc trythực thi bị dừng lại và dòng điều khiển chảy đến đầu catch(err). Biến err (chúng ta có thể sử dụng bất kỳ tên cho nó) sẽ chứa một đối tượng lỗi với các chi tiết về những gì đã xảy ra.

Vì vậy, một lỗi bên trong khốitry {…} không giết chết tập lệnh – chúng ta có cơ hội xử lý nó trong catch.

Hãy xem xét một số ít ví dụ .

  • Một ví dụ không có lỗi: hiển thị alert (1)(2):
/*
Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
@author cafedevn
Contact: [email protected]
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/
*/

try {

  alert('Start of try runs');  // (1) <--

  // ...no errors here

  alert('End of try runs');   // (2) <--

} catch(err) {

  alert('Catch is ignored, because there are no errors'); // (3)

}

Một ví dụ có lỗi: hiển thị (1)(3):

try {

  alert('Start of try runs');  // (1) <--

  lalala; // error, variable is not defined!

  alert('End of try (never reached)');  // (2)

} catch(err) {

  alert(`Error has occurred!`); // (3) <--

}

try..catch chỉ hoạt động cho các lỗi trong thời gian chạy

Để try..catchlàm việc, code phải được chạy. Nói cách khác, nó phải là JavaScript hợp lệ.

Nó sẽ không hoạt động giải trí nếu code sai về mặt cú pháp, ví dụ, nó có dấu ngoặc nhọn không khớp :

try {
  {{{{{{{{{{{{
} catch(e) {
  alert("The engine can't understand this code, it's invalid");
}

Công cụ JavaScript thứ nhất đọc code và sau đó chạy nó. Các lỗi xảy ra trong quy trình tiến độ đọc được gọi là lỗi thời hạn nghiên cứu và phân tích và không hề hồi sinh ( từ bên trong code đó ). Đó là chính bới động cơ không hề hiểu được code .

Vì vậy, try..catchchỉ có thể xử lý các lỗi xảy ra trong mã hợp lệ. Các lỗi như vậy được gọi là lỗi thời gian chạy và được ..catch bắt.

Nếu có một ngoại lệ xảy ra trong code trong quá trình chạy, như trong setTimeout, thì try..catchsẽ không bắt được:

try {
  setTimeout(function() {
    noSuchVariable; // script will die here
  }, 1000);
} catch (e) {
  alert( "won't work" );
}

Đó là bởi vì hàm chính được thực thi sau đó, khi động cơ đã rời khỏi cấu trúctry..catch.

Để bắt một ngoại lệ bên trong một hàm được lên lịch, try..catchphải nằm trong hàm đó:

setTimeout(function() {
  try {
    noSuchVariable; // try..catch handles the error!
  } catch {
    alert( "error is caught here!" );
  }
}, 1000);

2. Đối tượng lỗi

Khi xảy ra lỗi, JavaScript sẽ tạo một đối tượng chứa các chi tiết về nó. Đối tượng sau đó được chuyển qua làm đối số cho catch:

try {
  // ...
} catch(err) { // <-- the "error object", could use another word instead of err
  // ...
}

Đối với toàn bộ những lỗi tích hợp, đối tượng người tiêu dùng lỗi có hai thuộc tính chính :

name Tên lỗi. Ví dụ, đối với một biến không xác định đó là "ReferenceError". message

Tin nhắn văn bản về cụ thể lỗi .
Có những thuộc tính không phải chuẩn khác có sẵn trong hầu hết những thiên nhiên và môi trường. Một trong những sử dụng và tương hỗ thoáng rộng nhất là :

stack

Ngăn xếp lệnh gọi hiện tại : một chuỗi với thông tin về chuỗi những cuộc gọi lồng nhau dẫn đến lỗi. Được sử dụng cho mục tiêu gỡ lỗi .
Ví dụ :

try {
  lalala; // error, variable is not defined!
} catch(err) {
  alert(err.name); // ReferenceError
  alert(err.message); // lalala is not defined
  alert(err.stack); // ReferenceError: lalala is not defined at (...call stack)

  // Can also show an error as a whole
  // The error is converted to string as "name: message"
  alert(err); // ReferenceError: lalala is not defined
}

3. Tùy chọn “catch”

Nếu chúng ta không cần chi tiết lỗi, catchcó thể bỏ qua:

try {
  // ...
} catch { // <-- without (err)
  // ...
}

4. Dùng “try…catch”

Hãy khám phá một trường hợp sử dụng thực tế của try..catch.

Như tất cả chúng ta đã biết, JavaScript tương hỗ phương pháp JSON.parse ( str ) để đọc những giá trị được code hóa JSON .
Thông thường, nó được sử dụng để giải thuật tài liệu nhận được qua mạng, từ sever hoặc nguồn khác .

Chúng tôi nhận được nó và gọi JSON.parsenhư thế này:

let json = '{"name":"John", "age": 30}'; // data from the server

let user = JSON.parse(json); // convert the text representation to JS object

// now user is an object with properties from the string
alert( user.name ); // John
alert( user.age );  // 30

Bạn hoàn toàn có thể tìm thấy thông tin cụ thể hơn về JSON trong những phương pháp JSON, chương toJSON .

Nếu jsonkhông đúng định dạng, JSON.parsesẽ tạo ra lỗi, do đó, tập lệnh chết chết.

Chúng ta có nên hài lòng với điều đó ? Dĩ nhiên là không !
Bằng cách này, nếu có gì đó không đúng với tài liệu, khách truy vấn sẽ không khi nào biết điều đó ( trừ khi họ mở bảng điều khiển và tinh chỉnh dành cho developer ). Và mọi người thực sự không thích khi một thứ gì đó chỉ chết mà không có thông tin lỗi .

Hãy sử dụng try..catchđể xử lý lỗi:

let json = "{ bad json }";

try {

  let user = JSON.parse(json); // <-- when an error occurs...
  alert( user.name ); // doesn't work

} catch (e) {
  // ...the execution jumps here
  alert( "Our apologies, the data has errors, we'll try to request it one more time." );
  alert( e.name );
  alert( e.message );
}

Ở đây chúng ta chỉ sử dụng khốicatch để hiển thị thông báo, nhưng chúng ta có thể làm nhiều hơn thế: gửi yêu cầu mạng mới, đề xuất một giải pháp thay thế cho khách truy cập, gửi thông tin về lỗi đến cơ sở ghi nhật ký, lỗi. Tất cả tốt hơn nhiều so với chỉ chết.

5. Ném lỗi của chúng ta

Điều gì xảy ra nếu jsonđúng về mặt cú pháp, nhưng không có thuộc tínhname bắt buộc ?

Như thế này :

let json = '{ "age": 30 }'; // incomplete data

try {

  let user = JSON.parse(json); // <-- no errors
  alert( user.name ); // no name!

} catch (e) {
  alert( "doesn't execute" );
}

Ở đây JSON.parsechạy bình thường, nhưng sự vắng mặt namethực sự là một lỗi cho chúng ta.

Để thống nhất xử lý lỗi, chúng ta sẽ sử dụng toán tửthrow.

6. Toán tử Throw

Các toán tửthrow tạo ra một lỗi.

Cú pháp là :

throw 

Về mặt kỹ thuật, chúng ta có thể sử dụng bất cứ thứ gì như một đối tượng lỗi. Đó có thể là ngay cả một kiểu nguyên thủy, giống như một số hoặc một chuỗi, nhưng nó tốt hơn để đối tượng sử dụng, tốt hơn với namevà thuộc tínhmessage.

JavaScript có nhiều built-in constructors cho sai số chuẩn: Error, SyntaxError, ReferenceError, TypeErrorvà những cái khác. Chúng ta có thể sử dụng chúng để tạo các đối tượng lỗi là tốt.

Cú pháp của họ là :

let error = new Error(message);
// or
let error = new SyntaxError(message);
let error = new ReferenceError(message);
// ...

Đối với các lỗi tích hợp (không phải cho bất kỳ đối tượng nào, chỉ cho các lỗi), thuộc tínhname chính xác là tên của hàm tạo. Và messageđược lấy từ các đối số.

Ví dụ :

let error = new Error("Things happen o_O");

alert(error.name); // Error
alert(error.message); // Things happen o_O

Hãy xem loại lỗi nào JSON.parsetạo ra:

try {
  JSON.parse("{ bad json o_O }");
} catch(e) {
  alert(e.name); // SyntaxError
  alert(e.message); // Unexpected token b in JSON at position 2
}

Như chúng ta có thể thấy, đó là một SyntaxError.

Và trong trường hợp của chúng ta, sự vắng mặt namelà một lỗi, vì người dùng phải có một name.

Vì vậy, hãy ném ra nó :

/*
Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
@author cafedevn
Contact: [email protected]
Fanpage: https://www.facebook.com/cafedevn
Group: https://www.facebook.com/groups/cafedev.vn/
Instagram: https://instagram.com/cafedevn
Twitter: https://twitter.com/CafedeVn
Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
Pinterest: https://www.pinterest.com/cafedevvn/
YouTube: https://www.youtube.com/channel/UCE7zpY_SlHGEgo67pHxqIoA/
*/

let json = '{ "age": 30 }'; // incomplete data

try {

  let user = JSON.parse(json); // <-- no errors

  if (!user.name) {
    throw new SyntaxError("Incomplete data: no name"); // (*)
  }

  alert( user.name );

} catch(e) {
  alert( "JSON Error: " + e.message ); // JSON Error: Incomplete data: no name
}

Trong dòng (*), toán tửthrow tạo một SyntaxError cho message, giống như cách JavaScript sẽ tự tạo. Việc thực hiện tryngay lập tức dừng lại và dòng điều khiển nhảy vào catch.

Bây giờ catchđã trở thành một nơi duy nhất cho tất cả các xử lý lỗi: cả cho JSON.parsevà các trường hợp khác.

7. Rethrowing

Trong ví dụ trên, chúng ta sử dụng try..catchđể xử lý dữ liệu không chính xác. Nhưng có thể có một lỗi không mong muốn khác xảy ra trong khối try {...} không? Giống như một lỗi lập trình (biến không được xác định) hoặc một cái gì đó khác, không chỉ điều này dữ liệu không chính xác.

Ví dụ:

let json = '{ "age": 30 }'; // incomplete data

try {
  user = JSON.parse(json); // <-- forgot to put "let" before user

  // ...
} catch(err) {
  alert("JSON Error: " + err); // JSON Error: ReferenceError: user is not defined
  // (no JSON Error actually)
}

Tất nhiên, mọi thứ đều hoàn toàn có thể ! Lập trình viên làm sai. Ngay cả trong những tiện ích nguồn mở được sử dụng bởi hàng triệu người trong nhiều thập kỷ – đùng một cái một lỗi hoàn toàn có thể được phát hiện dẫn đến những vụ hack kinh khủng .

Trong trường hợp của chúng tôi, try..catchcó nghĩa là bắt lỗi dữ liệu không chính xác về lỗi dữ liệu Nhưng theo bản chất của nó, catchnhận được tất cả các lỗi từ try. Ở đây, nó nhận được một lỗi không mong muốn, nhưng vẫn hiển thị cùng một thông báo"JSON Error". Điều đó sai và cũng làm cho code khó gỡ lỗi hơn.

May mắn thay, chúng ta có thể tìm ra lỗi nào chúng ta nhận được, ví dụ từ lỗi của nó name:

try {
  user = { /*...*/ };
} catch(e) {
  alert(e.name); // "ReferenceError" for accessing an undefined variable
}

Quy tắc rất đơn thuần :

Catch chỉ nên xử lý các lỗi mà nó biết và tải lại tất cả các lỗi khác.

Kỹ thuật hoàn toàn có thể được lý giải chi tiết cụ thể hơn như :

  1. Bắt được tất cả các lỗi.
  2. Trong khốicatch(err) {...} chúng tôi phân tích các đối tượng lỗi err.
  3. Nếu chúng ta không biết cách xử lý nó, chúng ta sẽ làm throw err.

Trong code bên dưới, chúng ta sử dụng tính năng kiểm tra lại để catchchỉ xử lý SyntaxError:

/*
Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
@author cafedevn
Contact: [email protected]
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/
*/


let json = '{ "age": 30 }'; // incomplete data
try {

  let user = JSON.parse(json);

  if (!user.name) {
    throw new SyntaxError("Incomplete data: no name");
  }

  blabla(); // unexpected error

  alert( user.name );

} catch(e) {

  if (e.name == "SyntaxError") {
    alert( "JSON Error: " + e.message );
  } else {
    throw e; // rethrow (*)
  }

}

Lỗi ném (*)từ bên trong khốicatch rơi ra khỏi try..catchvà có thể bị bắt bởi một cấu trúctry..catch bên ngoài (nếu nó tồn tại) hoặc nó giết chết tập lệnh.

Vì vậy, khốicatch thực sự chỉ xử lý các lỗi mà nó biết cách xử lý và bỏ qua tất cả các lỗi khác.

Ví dụ dưới đây cho thấy các lỗi như vậy có thể bị bắt bởi một cấp độ nữa try..catch:

function readData() {
  let json = '{ "age": 30 }';

  try {
    // ...
    blabla(); // error!
  } catch (e) {
    // ...
    if (e.name != 'SyntaxError') {
      throw e; // rethrow (don't know how to deal with it)
    }
  }
}

try {
  readData();
} catch (e) {
  alert( "External catch got: " + e ); // caught it!
}

Ở đây readDatachỉ biết cách xử lý SyntaxError, còn bên ngoài try..catchbiết cách xử lý mọi việc.

8. try…catch…finally

Đợi đã, đó không phải là toàn bộ .

Cấu rúc try..catch có thể có thêm một mệnh đề : finally.

Nếu nó sống sót, nó chạy trong mọi trường hợp :

  • sau đó try, nếu không có lỗi
  • sau catch, nếu có lỗi

Cú pháp lan rộng ra trông như thế này :


try {
   ... try to execute the code ...
} catch(e) {
   ... handle errors ...
} finally {
   ... execute always ...
}

Hãy thử chạy code này :

try {
  alert( 'try' );
  if (confirm('Make an error?')) BAD_CODE();
} catch (e) {
  alert( 'catch' );
} finally {
  alert( 'finally' );
}

Code này có hai cách thực thi :

  1. Nếu bạn trả lời “Có” khi hỏi “Make an error?”, Sau đó try -> catch -> finally.
  2. Nếu bạn nói là No, thì try -> finally.

Các điều khoảnfinally thường được sử dụng khi chúng ta bắt đầu làm một cái gì đó và muốn hoàn thành nó trong bất kỳ trường hợp kết quả.

Chẳng hạn, chúng ta muốn đo thời gian mà hàm số Fibonacci fib(n). Đương nhiên, chúng ta có thể bắt đầu đo trước khi nó chạy và kết thúc sau đó. Nhưng nếu có lỗi trong khi gọi hàm thì sao? Cụ thể, việc thực hiện fib(n)trong code dưới đây trả về lỗi cho các số âm hoặc không nguyên.

Điều khoảnfinally này là một nơi tuyệt vời để hoàn thành các phép đo bất kể điều gì.

Ở đây finallyđảm bảo rằng thời gian sẽ được đo chính xác trong cả hai tình huống – trong trường hợp thực hiện thành công fibvà trong trường hợp có lỗi trong đó:

/*
Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
@author cafedevn
Contact: [email protected]
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/
*/

let num = +prompt("Enter a positive integer number?", 35)

let diff, result;

function fib(n) {
  if (n < 0 || Math.trunc(n) != n) {
    throw new Error("Must not be negative, and also an integer.");
  }
  return n <= 1 ? n : fib(n - 1) + fib(n - 2);
}

let start = Date.now();

try {
  result = fib(num);
} catch (e) {
  result = 0;
} finally {
  diff = Date.now() - start;
}

alert(result || "error occurred");

alert( `execution took ${diff}ms` );

Bạn có thể kiểm tra bằng cách chạy code bằng cách nhập 35vào prompt– nó sẽ thực thi bình thường, try sau đófinally. Và sau đó nhập -1– sẽ có một lỗi ngay lập tức và việc thực thi sẽ diễn ra 0ms. Cả hai phép đo đều được thực hiện chính xác.

Nói cách khác, hàm có thể kết thúc bằng returnhoặc throw, điều đó không quan trọng. Điều khoản thực thifinally trong cả hai trường hợp.

Các biến là cục bộ bên trong try..catch..finally

Xin lưu ý rằng resultdiffcác biến trong code ở trên được khai báo trước try..catch .

Mặt khác, nếu chúng ta khai báo lettrong khốitry, nó sẽ chỉ hiển thị bên trong nó.finallyreturn

finally làm việc cho bất kỳ lối ra từ try..catch. Điều đó bao gồm một rõ ràng một return.

Trong ví dụ dưới đây, có một returntrong try. Trong trường hợp này, finallyđược thực thi ngay trước khi điều khiển trở về code bên ngoài.

function func() {

  try {
    return 1;

  } catch (e) {
    /* ... */
  } finally {
    alert( 'finally' );
  }
}

alert( func() ); // first works alert from finally, and then this one

try..finally

Cấu trúctry..finally, không có mệnh đềcatch, cũng hữu ích. Chúng ta áp dụng nó khi chúng ta không muốn xử lý lỗi ở đây (để chúng xảy ra), nhưng muốn chắc chắn rằng các quy trình mà chúng ta đã bắt đầu được hoàn tất.

function func() {
  // start doing something that needs completion (like measurements)
  try {
    // ...
  } finally {
    // complete that thing even if all dies
  }
}

Trong đoạn code trên, một lỗi bên trong tryluôn rơi ra, bởi vì không có catch. Nhưng finallyhoạt động trước khi dòng thực thi rời khỏi hàm.

9. Catch toàn cầu

Đặc thù môi trường

tin tức từ phần này không phải là một phần của JavaScript cốt lõi .

Hãy tưởng tượng chúng ta đã có một lỗi nghiêm trọng bên ngoài try..catchvà kịch bản đã chết. Giống như một lỗi lập trình hoặc một số điều khủng khiếp khác.

Có cách nào để phản ứng về những sự cố như vậy không ? Chúng ta hoàn toàn có thể muốn ghi lại lỗi, hiển thị một cái gì đó cho người dùng ( thường thì họ không thấy thông tin lỗi ), v.v.

Không có gì trong đặc tả, nhưng môi trường thường cung cấp nó, bởi vì nó thực sự hữu ích. Chẳng hạn, Node.js process.on("uncaughtException")dành cho điều đó. Và trong trình duyệt, chúng ta có thể gán một hàm cho thuộc tính window.onerror đặc biệt, nó sẽ chạy trong trường hợp có lỗi.

Cú pháp :

window.onerror = function(message, url, line, col, error) {
  // ...
};

message
Thông báo lỗi.
url
URL của tập lệnh nơi xảy ra lỗi.
line, col
Số dòng và cột nơi xảy ra lỗi.
error
Đối tượng lỗi.

Ví dụ :

Vai trò của trình xử lý toàn cầu window.onerrorthường không phải là phục hồi thực thi tập lệnh – Nó không thể làm gì với code trong trường hợp có lỗi lập trình, nhưng gửi thông báo lỗi cho developer.

Ngoài ra còn có những dịch vụ web cung ứng ghi nhật ký lỗi cho những trường hợp như vậy, như https://errorception.com or http://www.muscula.com .
Họ thao tác như thế này :

  1. Chúng ta đăng ký tại dịch vụ và nhận một đoạn code (hoặc URL tập lệnh) từ chúng để chèn vào các trang.
  2. Tập lệnh JS đó thiết lập một window.onerrorchức năng tùy chỉnh .
  3. Khi xảy ra lỗi, nó sẽ gửi một yêu cầu mạng về nó tới dịch vụ.
  4. Chúng ta có thể đăng nhập vào giao diện web dịch vụ và thấy lỗi.

10. Tóm lược

Cấu trúctry..catch cho phép xử lý lỗi thời gian chạy. Theo nghĩa đen, nó cho phép dùng thử các ứng dụng và chạy các lỗi có thể xảy ra trong đó.

Cú pháp là :

try {
  // run this code
} catch(err) {
  // if an error happened, then jump here
  // err is the error object
} finally {
  // do in any case after try/catch
}

Có thể không có phầncatch hoặc không finally, vì vậy các cấu trúc ngắn hơn try..catchtry..finallycũng hợp lệ.

Các đối tượng người tiêu dùng lỗi có những thuộc tính sau :

  • message – thông báo lỗi có thể đọc được của con người.
  • name – chuỗi có tên lỗi (tên hàm xây dựng lỗi).
  • stack (không chuẩn, nhưng được hỗ trợ tốt) – ngăn xếp tại thời điểm tạo lỗi.

Nếu không cần một đối tượng lỗi, chúng ta có thể bỏ qua nó bằng cách sử dụng catch {thay vì catch(err) {.

Chúng ta cũng có thể tạo ra lỗi của chúng ta bằng cách sử dụng toán tử throw. Về mặt kỹ thuật, đối số throwcó thể là bất cứ điều gì, nhưng thông thường, đó là một đối tượng lỗi kế thừa từ lớpError tích hợp. Xem Thêm về mở rộng lỗi trong chương tiếp theo.

Rethrowing là một mô hình xử lý lỗi rất quan trọng: một khối catch thường mong đợi và biết cách xử lý loại lỗi cụ thể, vì vậy nó nên điều chỉnh lại các lỗi mà nó không biết.

Ngay cả khi chúng ta không có try..catch, hầu hết các môi trường đều cho phép chúng ta thiết lập trình xử lý lỗi toàn cầu để bắt lỗi khi mà rơi ra. Trong trình duyệt, đó là window.onerror.

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à có ích, bạn hoàn toàn có thể tham gia những 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!