openplanning

Hướng dẫn xử lý lỗi trong JavaScript

  1. Error là gì?
  2. Bắt lỗi thông qua try-catch
  3. Khối try-catch-finally
  4. Built-in Errors
  5. Ném ra Error
  6. Các Property của Error
  7. Ném tiếp lỗi (Re-throw Error)
  8. Các ngoại lệ khi bắt lỗi

1. Error là gì?

Trước hết chúng ta hãy xem một ví dụ, trong ví dụ này tôi gọi một phương thức của một đối tượng, nhưng đối tượng này thực sự không có phương thức này, và lỗi đã xẩy ra.
hello-error-example.js
console.log("Three");

let myObj = {};

console.log("Two");

console.log("One");

// Đối tương myObj không có phương thức showMe().
// Nhưng chúng ta gọi phương thức showMe().
// Và lỗi xẩy ra tại đây.
myObj.showMe(); // ==> Error!!!!!!!!! 

// Dòng mã dưới đây sẽ không được thực thi.
console.log("Let's go!");
Kết quả chạy ví dụ:
Bạn có thể thấy thông báo lỗi trên màn hình Console, thông báo lỗi rất rõ ràng, xẩy ra ở dòng thứ mấy trên code.
Hãy xem luồng đi của chương trình qua hình minh họa dưới đây.
  • Chương trình đã chạy hoàn toàn bình thường từ các bước (1), (2), (3), (4)
  • Bước thứ (5) xẩy ra lỗi khi bạn cố gắng gọi một phương thức của đối tượng, trong khi đối tượng này không có phương thức đó.
  • Code tại bước (6) sẽ không được thực thi.

2. Bắt lỗi thông qua try-catch

Các lỗi có thể xẩy ra trong thời gian chạy (runtime) của chương trình, nó có thể là một lỗi nằm ngoài dự tính của bạn. Sử dụng try-catch giúp bạn bắt và xử lý lỗi khi nó xẩy ra.
Quay trở lại với ví dụ ở trên, chúng ta thêm vào try-catch để bắt và xử lý lỗi.
hello-catch-example.js
console.log("Three");

let myObj = {};

console.log("Two");

console.log("One");

try {
    // Đối tương myObj không có phương thức showMe().
    // Nhưng chúng ta gọi phương thức showMe().
    // Và lỗi xẩy ra tại đây.
    myObj.showMe(); // ==> Error!

    // Code này sẽ bị bỏ qua
    console.log("!!!"); 
} catch (e) {
    console.log("Catched error: " + e);
    console.log("OK continue...");
} 
console.log("Let's go!");
Và kết quả chạy ví dụ:
Three
Two
One
Catched error: TypeError: myObj.showMe is not a function
OK continue...
Let's go!
Hình minh họa dưới đây giải thích về luồng đi (flow) của chương trình:
  • Các bước (1)-(4) hoàn toàn bình thường.
  • Ngoại lệ xẩy ra tại bước (5), khi bạn cố gắng gọi một phương thức của đối tượng, trong khi đối tượng này không có phương thức này.
  • Lập tức nó nhẩy vào thực thi lệnh trong khối catch, bước (6) bị bỏ qua.
  • Bước (7), (8) sẽ được thực thi.
  • Bước (9 sẽ được thực thi.

3. Khối try-catch-finally

Trên kia chúng ta đã làm quen với việc bắt lỗi thông qua khối try-catch. Việc xử lý lỗi đầy đủ là try-catch-finally. Khối finally luôn được thực thi bất kể lỗi có xẩy ra tại khối try hay không.
try {  
   // Làm gì đó tại đây.
} catch (e ) { 
   // Làm gì đó tại đây.
} finally  {
   // Khối finally luôn luôn được thực thi.
   // Làm gì đó tại đây.
}
Ví dụ:
try-catch-finally-example.js
function getGreeting(language) {
    try {
        console.log("Code in try block (*)"); 
        // Gọi phương thức greeting() của đối tượng 'language'.
        // Một ngoại lệ có thể bị ném ra tại đây
        // nếu đối tượng này không có phương thức greeting()
        let v = language.greeting(); 
        console.log("Code in try block (**)"); 
        return v; 
    } catch (e) { 
        console.log("Code in catch block. Something Error: " + e); 
    } finally {
        // Khối finally luôn luôn được thực thi.
        console.log("Code in finally block");
    } 
    return " !! ";
} 
// ----------------------- TEST --------------------------------- 
// Test 1:
console.log("----- Call getGreeting(null) -----"); 
let v1 = getGreeting(null);
console.log("Greeting: " + v1);  
// Test 2:
console.log("------ Call getGreeting(language) ------"); 
let language = new Object(); 
language.greeting = function() {
    return "Hello Everybody";
} 
let v2 = getGreeting(language); 
console.log("Greeting: " + v2);
Chạy ví dụ:
----- Call getGreeting(null) -----
Code in try block (*)
Code in catch block. Something Error: TypeError: Cannot read property 'greeting' of null
Code in finally block
Greeting:  !!
----- Call getGreeting(language) -----
Code in try block (*)
Code in try block (**)
Code in finally block
Greeting: Hello Everybody
Hình minh họa dưới đây minh họa luồng đi của chương trình nếu lỗi xẩy ra trong khối try, khối finally luôn luôn được thực thi.
Hình dưới đây là luồng đi của chương trình khi không có lỗi nào xẩy ra trong khối try. Trường hợp này khối finally sẽ được thực thi ngay trước khi lệnh return của khối try được thực thi.

4. Built-in Errors

ECMAScript có sẵn một vài lớp để đại diện cho một lỗi, dưới đây là hệ thống phân cấp (heirachy) của chúng.
RangeError
Một RangeError được ném ra nếu bạn sử dụng một con số nằm ngoài một phạm vi cho phép.
error-RangeError-example.js
let num = 1;
try {

  // A number cannot have 500 significant digits
  num.toPrecision(500); // ==> RangeError!!
 
}
catch(err) {
  console.log(err.name);
  console.log(err);
}
ReferenceError
Một ReferenceError sẽ ném ra (throw) nếu bạn sử dụng một biến mà nó chưa được khai báo.
error-ReferenceError-example.js
var x;
try {
  x = y + 1;   // y cannot be referenced (used)
}
catch(err) {
   console.log("Error Name: "+ err.name);
   console.log(err);
}
SyntaxError
Một SyntaxError được ném ra (throw) nếu bạn cố gắng đánh giá (evaluate) một đoạn code mà đoạn code đó sai cú pháp.
error-SyntaxError-example.js
try {
  let x;
  eval(" x  = 'Hello  ");   // Missing ' will produce an error
}
catch(err) {
  console.log("Error Name: " + err.name);
  console.log(err);
}
TypeError
Một TypeError được ném ra (throw) nếu bạn sử dụng một giá trị không thuộc kiểu mong đợi. Chẳng hạn lỗi xẩy ra khi bạn gọi một phương thức của một đối tượng, trong khi đối tượng này không có phương thức đó.
error-TypeError-example.js
var num = 1;
try {
  num.toUpperCase(); // Number has no method toUpperCase()
}
catch(err) {
  console.log("Error Name: " + err.name);
  console.log(err);
}
URIError
Một URIError được ném ra (throw) nếu bạn sử dụng các ký tự không hợp lệ trong hàm URI:
error-URIError-example.js
try {
  decodeURI("%%%");   // You cannot URI decode percent signs
}
catch(err) {
  console.log("Error Name: " + err.name);
  console.log(err);
}

5. Ném ra Error

ECMAScript cho phép bạn ném ra (throw) bất kỳ một cái gì đó trong thời gian chạy của chương trình, chương trình sẽ coi như vừa có một lỗi xẩy ra.
throw-any-example.js
console.log(" -------- Test throw any object ------------");
try {
   let myObj = {};
   throw myObj;
} catch(e)  {
   console.log("Catch error: ");
   console.log(e);
}
console.log(" -------- Test throw a Symbol ------------");
try {
   let mySymbol = Symbol();
   throw mySymbol;
} catch(e)  {
   console.log("Catch error: ");
   console.log(e);
}
console.log(" -------- Test throw a Number ------------");
try {
   let myNumber = 100;
   throw myNumber;
} catch(e)  {
   console.log("Catch error: ");
   console.log(e);
}
console.log(" -------- Test throw a String ------------");
try {
   let myString = "Some error";
   throw myString;
} catch(e)  {
   console.log("Catched error: ");
   console.log(e);
}
Output:
-------- Test throw any object ------------
Catch errorr:
{}
 -------- Test throw a Symbol ------------
Catch error:
Symbol()
 -------- Test throw a Number ------------
Catch error:
100
 -------- Test throw a String ------------
Catched error:
Some error
Thông thường bạn sẽ tạo sử dụng lớp Error để tạo ra một đối tượng lỗi. Các lớp khác như SyntaxError, InternalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError cũng có thể được sử dụng trong ngữ cảnh phù hợp.
Một đối tượng lỗi được tạo ra thông qua lớp Error (hoặc các lớp con của nó), khi được ném ra (throw) nó sẽ chứa các thông tin quan trọng như tập tin mà lỗi phát sinh, vị trí phát sinh lỗi, và các thông tin giúp bạn dò lỗi.
throw-error-example.js
console.log("Three");

// Create an Error
let myError = new Error("Something error!");
console.log("Two");
// Throw it!
throw myError;
console.log("One");
Trong trường hợp đơn giản bạn có thể ném ra một thứ bất kỳ, không phải là đối tượng của lớp Error (Hoặc các lớp con của nó). Tuy nhiên khi bắt các lỗi kiểu này bạn sẽ không có các thông tin như tập tin phát ra lỗi, vị trí phát sinh lỗi,...
throw-string-error-example.js
console.log("Three");
try {
  console.log("Two");
  // Throw a String!
  throw "Some error!!";
} catch(e)  {
   console.log("typeof e = " + (typeof e));
   // Log the error
   console.log(e); // Some error!!
}
console.log("One");
Output:
Three
Two
typeof e = string
Some error!!
One
Trong ECMAScript, mỗi khối try có tương ứng duy nhất một khối cache. Nhưng trong khối try có thể có nhiều kiểu lỗi xẩy ra, trong trường hợp này bạn cần phải kiểm tra lỗi bắt được trong khối catch để đưa ra cách xử lý phù hợp tương ứng với từng loại lỗi.
catch-complex-example.js
let err = new Error("My Error");
let rangeErr = new RangeError();
let evalErr = new EvalError("My Eval Error");

// A random value in [0.. 9]
let randomValue = Math.floor(Math.random() * 10);

// [0,1,2,3]
let random0123 = randomValue % 4;
console.log("random0123 = " + random0123);
try {
   if(random0123 == 0) {
      throw err;
   } else if(random0123 == 1){
      throw rangeErr;
   } else if(random0123 == 2)  {
      throw evalErr;
   } else if(random0123 == 3)  {
      throw "A String Error";
   }
} catch(e)  {
   console.log("typeof e = " + (typeof e));// 'object' or 'string'
   if(e instanceof RangeError) {
      console.log("--> RangeError!!");
   } else if(e instanceof EvalError) {
      console.log("--> EvalError!!");
   } else if(e instanceof Error) {
      console.log("--> Error!!");
   } else if (typeof e == "string"){
      console.log("--> String Error!!");
   } else  {
      console.log("--> Error!!");
   }
   console.log(e);
}

6. Các Property của Error

Trong ECMAScript, Lỗi mà bạn bắt được có thể là một "Error object" hoặc một kiểu dữ liệu bất kỳ. Nếu là một "Error object" bạn sẽ có các thông tin quan trọng như tên tập tin gây ra lỗi, vị trí lỗi, Stack Trace,..
Có một vài property quan trọng của lớp Error:
  • name: Tên của lỗi.
  • message: Nội dung lỗi.
  • stack (Readonly): là một chuỗi chứa thông tin giúp bạn dò ra vị trí phát sinh lỗi.
error-properties-example.js
// Create an Error
let myError = new Error();

myError.name = "MyError";
myError.message = "My Error String"; 
try {
  throw myError; 
} catch(err)  {
  console.log("Error Name: " + err.name);
  console.log("Error Message: " + err.message);
  console.log("Type of err.stack: " + (typeof err.stack));
  console.log("--- Stack Trace: ---");  
  console.log(err.stack);
}

7. Ném tiếp lỗi (Re-throw Error)

Trong khi xử lý ngoại lệ bạn có thể bắt ngoại lệ đó và xử lý hoặc có thể ném tiếp (rethrow) nó ra vòng ngoài.
rethrow-example.js
function checkScore(score) {
    if (score < 0 || score > 100) {
        throw "Invalid Score " + score;
    }
} 
function checkPlayer(name, score) { 
    try { 
        checkScore(score)
    } catch (e) {
        // Làm gì đó với exception
        console.log("Something invalid with player: " + name + " >> " + e); 
        // Sau đó ném tiếp ra bên ngoài.
        throw e;
    } 
    console.log("OK Player " + name + " has score: " + score );
}  
// --------------- TEST -------------- 
checkPlayer("Tom", 90); 
checkPlayer("Jerry", -10);
Ví dụ, bắt ngoại lệ và ném tiếp (rethrow) bởi một ngoại lệ khác.
rethrow-example2.js
function checkScore(score) {
    if (score < 0 || score > 100) {
        throw "Invalid Score " + score;
    }
} 
function checkPlayer(name, score) { 
    try { 
        checkScore(score)
    } catch (e) {
        // Làm gì đó với exception
        console.log("Something invalid with player: " + name + " >> " + e); 
        // Sau đó ném ra một ngoại lệ khác
        throw ("Score " + score +" invalid for player " + name);
    } 
    console.log("OK Player " + name + " has score: " + score );
}  
// --------------- TEST -------------- 
checkPlayer("Tom", 90); 
checkPlayer("Jerry", -10);

8. Các ngoại lệ khi bắt lỗi

Trong ECMAScript có những tình huống bạn nghĩ rằng sẽ có một lỗi phát sinh, nhưng nó lại không xẩy ra. Chẳng hạn phép chia một số cho 0 sẽ không gây ra lỗi, kết quả trả về là Infinity hoặc -Infinity.
ex-Infinity-example.js
console.log( typeof Infinity ); // number

let a = 1 / 0;
console.log(a); // Infinity

let b = -1 / 0;
console.log(b); // -Infinity
Lấy một giá trị không phải là số để chia cho một số, kết quả là NaN (Not a Number).
ex-NaN-example.js
console.log( typeof NaN ); // number
console.log( isNaN(1) ); // false
console.log( isNaN( NaN ) ); // true

let a = "A String" / 2;
console.log(a); // NaN

let obj = {};
let b = obj / 2;
console.log(b); // NaN
Bạn có thể truy cập vào một phần tử với chỉ số bất kỳ của một mảng (Array) mà không gây ra lỗi.
ex-array-index-example.js
let myArray = [1, 100, 20];

console.log( myArray[1]); // 100
console.log( myArray[10] ); // undefined
console.log( myArray[-10] ); // undefined

Các hướng dẫn ECMAScript, Javascript

Show More