Control flow and error handling

JavaScript hỗ trợ một tập hợp lệnh gọn nhẹ, các lệnh điều khiển chuyên biệt, mà khi kết hợp lại có thể tăng tính tương tác cho ứng dụng của bạn lên đáng kể. Chương này giới thiệu sơ qua về các lệnh này.

The JavaScript reference contains exhaustive details about the statements in this chapter. The semicolon (;) character is used to separate statements in JavaScript code.

Any JavaScript expression is also a statement. See Expressions and operators for complete information about expressions.

Block statement

Một trong những lệnh căn bản nhất là khối lệnh, được dùng để nhóm lại các câu lệnh. Khối lệnh được phân định bởi cặp dấu ngoặc nhọn:

{
  lệnh_1;
  lệnh_2;
  .
  .
  .
  lệnh_n;
}

Ví dụ

Khối lệnh thường được dùng với lệnh điều khiển (như là if, for, while).

while (x < 10) {
  x++;
}

Ở đây, { x++; } là một khối lệnh.

Quan trọng: Trước ECMAScript2015 (phiên bản thứ 6), JavaScript chưa có phạm vi khối. Biến khai báo trong khối được đặt phạm vi theo hàm hoặc đoạn mã bao bọc, hiệu ứng khi đặt lại chúng sẽ vượt ra khỏi phạm vi của khối. Nói cách khác, khối lệnh không định nghĩa phạm vi. Các khối độc lập ("standalone" - tức là đi kèm với lệnh điều khiển nào) trong JavaScript có thể sản sinh kết quả khác so với khối lệnh tương tự trong Java hay C. Chẳng hạn:

var x = 1;
{
  var x = 2;
}
console.log(x); // trả về 2

Đoạn mã này trả về 2 bởi vì câu lệnh var x bên trong khối có cùng phạm vi với câu lệnh var x trước khối. Trong C hoặc Java, đoạn mã này sẽ trả về 1.

Kể từ ECMAScript2015, hai kiểu khai báo biến mới là letconst đều được đặt trong phạm vi khối lệnh. Tham khảo letconst để biết thêm chi tiết.

Lệnh điều kiện

Lệnh điều kiện là tập hợp các dòng lệnh sẽ thực thi nếu điều kiện nào đó trả về true. JavaScript hỗ trợ hai lệnh điều kiện: if...elseswitch.

Lệnh if...else

Dùng mệnh đề if để thực thi lệnh nếu điều kiện ban đầu trả về true. Có thể dùng thêm mệnh đề else để thực thi lệnh nếu điều kiện trả về false. Lệnh if trông như sau:

if (điều_kiện) {
  lệnh_1;
} else {
  lệnh_2;
}

Ở đây, điều_kiện có thể là bất cứ biểu thức nào trả về true hoặc false. Xem Boolean để hiểu cách thức trả về truefalse. Nếu điều_kiện trả về true, lệnh_1 sẽ được thực thi; trái lại, lệnh_2 sẽ được thực thi. lệnh_1lệnh_2 có thể là bất cứ câu lệnh nào, bao gồm các câu lệnh if lồng nhau.

Bạn cũng có thể dùng lệnh else if để có một dãy điều kiện liên tiếp nhau, chẳng hạn như sau:

if (điều_kiện_1) {
  lệnh_1;
} else if (điều_kiện_2) {
  lệnh_2;
} else if (điều_kiện_n) {
  lệnh_n;
} else {
  lệnh_cuối;
} 

Trong trường hợp có nhiều điều kiện, chỉ thực thi các lệnh trong điều kiện đầu tiên trả về true. Để thực thi nhiều lệnh, hãy nhóm chúng lại trong a khối lệnh ({ ... }) . Nói tóm lại, ta luôn nên dùng khối lệnh, nhất là khi lồng trong câu lệnh if:

if (điều_kiện) {
  lệnh_1_nếu_điều_kiện_là_true;
  lệnh_2_nếu_điều_kiện_là_true;
} else {
  lệnh_3_nếu_điều_kiện_là_false;
  lệnh_4_nếu_điều_kiện_là_false;
}
Đừng dùng lệnh gán trong biểu thức điều kiện, bởi vì lệnh gán có thể gây ra nhiều hiệu ứng phụ. Chẳng hạn, đừng viết như sau:
if (x = y) {
  /* tập hợp các câu lệnh */
}

Nếu cần phải gán trong biểu thức điều kiện, hãy bọc chúng lại trong cặp dấu ngoặc tròn, chẳng hạn:

if ((x = y)) {
  /* tập hợp các câu lệnh */
}

Giá trị Falsy

Các giá trị sau sẽ trả về false (còn được gọi là giá trị Falsy):

  • false
  • undefined
  • null
  • 0
  • NaN
  • xâu rỗng ("")

Mọi giá trị khác, bao gồm tất cả object, trả về true khi được truyền vào câu lệnh điều kiện.

Đừng nhầm lẫn giữa giá trị Boolean sơ khai truefalse với giá trị true và false của object Boolean. Chẳng hạn:

var b = new Boolean(false);
if (b) // điều kiện này trả về true
if (b == true) // điều kiện này trả về false

Ví dụ

Trong ví dụ sau, hàm checkData trả về true nếu số lượng ký tự của object Text là 3; trái lại, một thông báo sẽ hiện ra và hàm sẽ trả về false.

function checkData() {
  if (document.form1.threeChar.value.length == 3) {
    return true;
  } else {
    alert('Enter exactly three characters. ' +
    document.form1.threeChar.value + ' is not valid.');
    return false;
  }
}

Lệnh switch

Lệnh switch statement allows a program to evaluate an expression and attempt to match the expression's value to a case label. If a match is found, the program executes the associated statement. A switch statement looks as follows:

switch (expression) {
  case label_1:
    statements_1
    [break;]
  case label_2:
    statements_2
    [break;]
    ...
  default:
    statements_def
    [break;]
}

Trước hết, chương trình sẽ tìm mệnh đề case với nhãn trùng với giá trị của biểu thức và truyền điểu khiển tới mệnh đề đó, thực thi các lệnh liên quan. Nếu không có nhãn nào khớp, chương trình sẽ tìm mệnh đề mặc định default, và nếu tìm thấy, chương trình sẽ truyền điểu khiển tới mệnh đề đó, thực thi các lệnh liên quan. Nếu không tìm thấy mệnh đề mặc định default, chương trình tiếp tục thực thi cho tới cuối lệnh switch. Theo quy ước, mệnh đề default là mệnh đề cuối cùng, nhưng không phải lúc nào cũng nên tuân theo.

Lệnh tuỳ chọn break liên kết với từng mệnh đề case nhằm đảm bảo chương trình nhảy ra khỏi lệnh switch ngay sau khi khớp được giá trị. Nếu không có break, chương trình sẽ thực thi lệnh tiếp theo trong switch.

Ví dụ

Trong ví dụ sau, nếu fruittype được gán giá trị "Bananas", chương trình sẽ khớp với nhãn "Bananas" và thực thi các lệnh đi kèm. Khi chương trình chạy tới break, chương trình ngừng và thực thi các lệnh sau khối lệnh switch. Nếu không có break, lệnh ứng với nhãn "Cherries" cũng sẽ được thực thi.

switch (fruittype) {
  case 'Oranges':
    console.log('Oranges are $0.59 a pound.');
    break;
  case 'Apples':
    console.log('Apples are $0.32 a pound.');
    break;
  case 'Bananas':
    console.log('Bananas are $0.48 a pound.');
    break;
  case 'Cherries':
    console.log('Cherries are $3.00 a pound.');
    break;
  case 'Mangoes':
    console.log('Mangoes are $0.56 a pound.');
    break;
  case 'Papayas':
    console.log('Mangoes and papayas are $2.79 a pound.');
    break;
  default:
   console.log('Sorry, we are out of ' + fruittype + '.');
}
console.log("Is there anything else you'd like?");

Lệnh xử lý ngoại lệ

Bạn có thể quăng ngoại lệ bằng lệnh throw và xử lý chúng bằng lệnh try...catch.

Kiểu ngoại lệ

Gần như mọi object đều có thể bị quăng ra trong JavaScript. Tuy vậy, không phải object nào khi quăng ra cũng tương tự nhau. Mặc dù số và ký tự hay được quăng ra khi gặp lỗi nhiều hơn, song sử dụng kiểu ngoại lệ chuyên biệt thường hiệu quả hơn nhiều:

Lệnh throw

Lệnh throw để quăng ra ngoại lệ. Khi muốn quăng ra ngoại lệ, bạn phải đặc tả biểu thức chứa giá trị để quăng ra:

throw expression;

Bạn có thể quăng ra biểu thức nào cũng được, không chỉ biểu thức dành riêng cho kiểu ngoại lệ. Ví dụ sau đây quăng ra vô số kiểu:

throw 'Error2';   // String type
throw 42;         // Number type
throw true;       // Boolean type
throw {toString: function() { return "I'm an object!"; } };
Ghi chú: Bạn có thể đặc tả object khi quăng ngoại lệ. Rồi đặt tham chiếu tới thuộc tính của object đó trong khối catch.
// Tạo object có kiểu UserException
function UserException(message) {
  this.message = message;
  this.name = 'UserException';
}

// Bắt ngoại lệ in ra dòng lỗi màu mè một tí 
// (như khi in lên console chả hạn)
UserException.prototype.toString = function() {
  return this.name + ': "' + this.message + '"';
}

// Tạo một instance của object rồi quăng ra
throw new UserException('Value too high');

Lệnh try...catch

Lệnh try...catch đánh dấu một khối để thử, và xác định khi nào sẽ quăng ra ngoại lệ. Khi ngoại lệ bị quăng ra, lệnh try...catch sẽ tóm gọn nó.

Lệnh try...catch bao gồm khối try, chứa một hoặc nhiều câu lệnh, và khối catch, chứa các lệnh dành để xử lý khi có ngoại lệ bị quăng ra trong khối try. Tức là, bạn muốn lệnh trong khối try thành công, và nhỡ nó không thành công, bạn muốn truyền xử lý xuống khối catch. Nếu bất cứ lệnh nào trong khối try (hoặc hàm được gọi trong khối try) quăng ra ngoại lệ, lệnh điều khiển sẽ nhảy thẳng xuống khối catch. Nếu không có ngoại lệ nào bị quăng ra trong khối try, khối catch sẽ bị bỏ qua. Khối finally thực thi sau khi khối trycatch thực thi xong nhưng trước các lệnh đặt ngay sau lệnh try...catch.

Ví dụ sau dùng lệnh try...catch. Trong ví dụ này, ta gọi một hàm nhận vào một giá trị và trả về tên tháng tương ứng. Nếu giá trị truyền vào không tương ứng với số tháng (1-12), một ngoại lệ sẽ bị quăng ra có kiểu "InvalidMonthNo" và lệnh trong khối catch sẽ đặt lại giá trị cho biến monthName thành unknown.

function getMonthName(mo) {
  mo = mo - 1; // Chỉnh lại số tháng cho hợp với chỉ số mảng (1 = Jan, 12 = Dec)
  var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul',
                'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
  if (months[mo]) {
    return months[mo];
  } else {
    throw 'InvalidMonthNo'; // dùng từ khoá throw ở đây nhé
  }
}

try { // lệnh để thử
  monthName = getMonthName(myMonth); // hàm để quăng ra ngoại lệ
}
catch (e) {
  monthName = 'unknown';
  logMyErrors(e); // truyền object ngoại lệ vào hàm xử lý lỗi của bạn
}

Khối catch

Bạn có thể dùng khối catch để xử lý tất cả ngoại lệ sinh ra trong khối try.

catch (catchID) {
  statements
}

Khối catch nhận vào một định danh (catchID như trong cú pháp phía trên) giữ giá trị mà lệnh throw quăng ra; bạn có thể dùng định danh này để lấy thông tin về ngoại lệ bị quăng ra. JavaScript tạo ra định danh này khi chương trình chạy vào khối catch; định danh này chỉ tồn tại trong khối catch; sau khi khối catch thực thi xong, định danh đó sẽ không còn tồn tại nữa.

Chẳng hạn, đoạn code sau quăng ra một ngoại lệ. Khi ngoại lệ xảy ra, điều khiển được truyền xuống khối catch.

try {
  throw 'myException'; // sinh ngoại lệ
}
catch (e) {
  // lệnh xử lý ngoại lệ
  logMyErrors(e); // truyền object ngoại lệ xuống hàm xử lý lỗi
}

Khối finally

Khối finally chứa các lệnh thực thi ngay sau khi thực thi khối trycatch nhưng trước các lệnh liền sau lệnh try...catch. Khối finally vẫn thực thi dù có xảy ra ngoại lệ hay không. Nếu ngoại lệ bị quăng ra, các lệnh trong khối finally vẫn thực thi dù khối catch có xử lý ngoại lệ hay không.

Bạn có thể dùng khối finally để khiến mã của bạn lỗi một cách yên bình nhỡ ngoại lệ xảy ra; chả hạn, bạn muốn giải phóng tài nguyên khỏi đoạn mã của mình. Ví dụ sau mở một tệp tin và thực thi lệnh dùng tài nguyên của tệp đó (JavaScript phía server cho phép bạn truy cập vào tệp tin). Nếu có ngoại lệ bị quăng ra trong lúc đang mở tệp tin, khối finally sẽ đóng đoạn tệp tin lại trước khi đoạn mã bị lỗi.

openMyFile();
try {
  writeMyFile(theData); // Ghi vào tệp, có thể có lỗi
} catch(e) {  
  handleError(e); // Nếu có lỗi thì dùng hàm này để xử lý
} finally {
  closeMyFile(); // Luôn luôn đóng tệp lại
}

Nếu khối finally trả về giá trị thì giá trị đó sẽ trở thành giá trị trả về của cả dãy lệnh try-catch-finally, bỏ qua mọi lệnh return trong khối trycatch:

function f() {
  try {
    console.log(0);
    throw 'bogus';
  } catch(e) {
    console.log(1);
    return true; // lệnh return ở đây bị tạm ngưng
                 // cho tới khi thực thi xong khối finally
    console.log(2); // không thực thi tới
  } finally {
    console.log(3);
    return false; // ghi đè lệnh "return" phía trên
    console.log(4); // không thực thi tới
  }
  // "return false" sẽ thực thi ngay lúc này  
  console.log(5); // không thực thi tới
}
console.log(f()); // 0, 1, 3, false 

Ghi đè giá trị trả về bằng khối finally cũng áp dụng với các ngoại lệ bị quăng ra trong khối catch:

function f() {
  try {
    throw 'bogus';
  } catch(e) {
    console.log('caught inner "bogus"');
    throw e; // lệnh thow này bị tạm ngưng 
             // cho tới khi thực thi xong khối finally
  } finally {
    return false; // ghi đè lệnh "throw" phía trên
  }
  // "return false" sẽ thực thi ngay lúc này
}

try {
  console.log(f());
} catch(e) {
  // khối này sẽ không bao giờ tới được
  // bởi vì khối catch phía trên đã bị ghi đè
  // bởi lệnh return trong finally
  console.log('caught outer "bogus"');
}

// ĐẦU RA
// caught inner "bogus"
// false

Lồng lệnh try...catch

Bạn có thể lồng một hoặc nhiều lệnh try...catch. Nếu lệnh try...catch bên trong không có khối catch, thì nó nên có khối finally và lệnh try...catch bọc bên ngoài phải bắt được cái gì đấy. Để biết thêm thông tin, hãy đọc lồng khối try trên trang try...catch.

Tận dụng object Error

Tuỳ theo kiểu của lỗi, bạn có thể dùng thuộc tính 'name' và 'message' để lấy ra thông điệp lỗi rõ ràng hơn. 'name' lấy ra class chung của Error (tức là 'DOMException' hoặc 'Error'), trong khi 'message' thường lấy ra thông điệp về lỗi súc tích hơn thông điệp tạo ra bởi ép object lỗi thành xâu ký tự.

Nếu bạn muốn quăng ra ngoại lệ của riêng mình, để tận dụng được những thuộc tính này (ví dụ như khi khối catch không phân biệt giữa ngoại lệ của bạn và của hệ thống), bạn có thể dùng hàm khởi tạo Error. Chẳng hạn:

function doSomethingErrorProne() {
  if (ourCodeMakesAMistake()) {
    throw (new Error('The message'));
  } else {
    doSomethingToGetAJavascriptError();
  }
}
....
try {
  doSomethingErrorProne();
} catch (e) {
  console.log(e.name); // logs 'Error'
  console.log(e.message); // logs 'The message' or a JavaScript error message
}