Cho dù chúng ta có giỏi lập trình đến đâu, đôi khi các tập lệnh của chúng ta cũng có lỗi. Chúng có thể xảy ra do lỗi của chúng ta, đầu vào của người dùng không mong muốn, phản hồi của máy chủ bị lỗi và vì hàng ngàn lý 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 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ố 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: 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/
*/

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 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 trước tiên đọc code và sau đó chạy nó. Các lỗi xảy ra trong giai đoạn đọc được gọi là lỗi thời gian phân tích và không thể phục hồi (từ bên trong code đó). Đó là bởi vì động cơ không thể 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 tất cả các lỗi tích hợp, đối tượ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ề chi tiết lỗi.

Có các thuộc tính không phải chuẩn khác có sẵn trong hầu hết các môi trường. Một trong những sử dụng và hỗ trợ rộng rãi 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 các cuộc gọi lồng nhau dẫn đến lỗi. Được sử dụng cho mục đích 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ư chúng ta đã biết, JavaScript hỗ trợ phương thức JSON.parse(str) để đọc các giá trị được code hóa JSON.

Thông thường, nó được sử dụng để giải mã dữ liệu nhận được qua mạng, từ máy chủ 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 có thể tìm thấy thông tin chi tiết hơn về JSON trong các phương thức 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 dữ liệu, khách truy cập sẽ không bao giờ biết điều đó (trừ khi họ mở bảng điều khiển 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 báo 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 <error object>

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: cafedevn@gmail.com
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 có thể! Lập trình viên làm sai. Ngay cả trong các tiện ích nguồn mở được sử dụng bởi hàng triệu người trong nhiều thập kỷ – đột nhiên một lỗi có thể được phát hiện dẫn đến các vụ hack khủng khiếp.

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 giả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 có thể được giải thích chi tiết 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: 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/
*/


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à tất cả.

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

Nếu nó tồn tại, 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 mở rộng 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 hiện:

  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: 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/
*/

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

Thông tin 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 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ường họ không thấy thông báo 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ụ:

<script>
  window.onerror = function(message, url, line, col, error) {
    alert(`${message}\n At ${line}:${col} of ${url}`);
  };

  function readData() {
    badFunc(); // Whoops, something went wrong!
  }

  readData();
</script>

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ó các dịch vụ web cung cấp ghi nhật ký lỗi cho các trường hợp như vậy, như https://errorception.com or http://www.muscula.com.

Họ làm việ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 lỗi có các 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à 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!