JavaScript

Xử lý lỗi với try/catch/finally trong JavaScript

Xử lý lỗi với try/catch/finally trong JavaScript
Được viết bởi Minh Hoàng

Series lập trình JavaScript, ngôn ngữ lập trình linh động, thực thi phía client.

Xử lý lỗi với try/catch trong JavaScript

– Lỗi là gần như không thể tránh khỏi trong lập trình nói chung và các chương trình JavaScript nói riêng. Lỗi có thể là lỗi coding do lập trình viên gây ra, lỗi do input sai và cả những lỗi không lường trước được. Thông thường trong JavaScript, nếu có lỗi, một vài điều có thể xảy ra:

  • Trình duyệt sẽ “lặng lẽ” (silently) tự động báo lỗi của mã JavaScript xung quanh nơi xảy ra lỗi không được thực thi. Chúng ta có thể nhấn phím F12 để xem những thông báo lỗi này.
  • Hoặc trình duyệt sẽ tạo ra các cửa sổ pop up gây phiền nhiễu cho người dùng để thông báo rằng đã xảy ra lỗi.

– Nhưng với những kiểu thông báo trên đều không phải là tùy chọn lý tưởng, là một nhà phát triển JavaScript, chúng ta cần dự đoán các lỗi và xử lý chúng một cách hiệu quả. Điều này cuối cùng sẽ giúp bạn tạo ra các chương trình mạnh mẽ, đáng tin cậy và hiệu quả. Một cách đơn giản để xử lý lỗi là thông qua câu lệnh try…catch…finally…

1. Khối lệnh try/catch/finally

1. Khối lệnh try/catch/finally

– Trong block try cho dù chứa một hoặc nhiều câu lệnh cũng đều phải đặt trong cặp dấu ngoặc nhọn {}. Theo sau đó bắt buộc phải có ít nhất block lệnh của catch hoặc block lệnh của finally. Do đó, sẽ có 3 dạng (forms) của câu lệnh try:

  1. try…catch…
  2. try…finally…
  3. try…catch…finally…

– Cú pháp:

try {
    Các lệnh thực thi
}
catch(err) {
    Các lệnh xử lý lỗi (handle errors)
} 
finally {
    Các lệnh ở đây sẽ luôn được thực hiện bất kể kết quả của try / catch
}

– Cách thức hoạt động:

  • Đầu tiên sẽ thực thi các lệnh của khối try.
  • Sau khi thực thi các lệnh của khối try. Nếu không có lỗi thì khối catch sẽ được bỏ qua, ngược lại nếu có lỗi thì các lệnh xử lý lỗi ở khối catch sẽ được thực thi.
  • Dù có lỗi hay không có lỗi thì các lệnh của khối finally luôn được thực thi.

Trường hợp chương trình có lỗi: nếu có xử lý lỗi với try/catch thì chương trình vẫn được thực hiện đến cuối cùng. Nếu không có try/catch thì chương trình sẽ stop ngay tại dòng gây ra lỗi.

– Ví dụ sau minh họa lỗi gọi hàm chưa được định nghĩa:

① Trường hợp không có try/catch:
function myFunction() {
    welcome("Welcome guest!");	// Vì phát sinh lỗi ở đây, nên chương trình sẽ stop tại dòng này,
    							// mà không tiếp tục thực thi các xử lý bên dưới.
    
    document.getElementById("message").innerHTML = "Minh Hoàng Blog | minhhn.com";
}
Try it »

② Trường hợp có try/catch:
function myFunction() {
    try { 
        welcome("Welcome guest!");
    }
    catch(err) {
        document.getElementById("err").innerHTML = "Lỗi gọi hàm chưa được định nghĩa!!!";
    }
    
    // Vì đã có xử lý lỗi, nên dòng này vẫn được thực thi
    document.getElementById("message").innerHTML = "Minh Hoàng Blog | minhhn.com";
}
Try it »

③ Tương đương với try/catch/finally:
function myFunction() {
    try { 
        welcome("Welcome guest!");
    }
    catch(err) {
        document.getElementById("err").innerHTML = "Lỗi gọi hàm chưa được định nghĩa!!!";
    }
    finally {
    	document.getElementById("message").innerHTML = "Minh Hoàng Blog | minhhn.com";
	}
}
Try it »
2. Đặc điểm của khối lệnh finally

2. Đặc điểm của khối lệnh finally

– Ngoài đặc điểm: sau khi thực thi các lệnh ở khối try dù có lỗi hay không có lỗi thì các lệnh của khối finally luôn được thực thi (như ví dụ ③), thì finally còn một đặc điểm khác khá đặc biệt mà chúng ta sẽ xét ngay sau đây.

– Các bạn có thể thấy ví dụ ② và ③ cho ra một kết quả như nhau, vậy chúng ta cần finally để làm gì? Giả sử bạn viết một hàm để trả về kết quả tính toán nào đó, nhưng sau lệnh return bạn còn muốn thực hiện xử lý abc, xyz,… nữa trước khi kết thúc hàm, chẳng hạn như close file hay dọn dẹp “rác” hủy các object đã khởi tạo,…
Khi đó, nếu bạn đặt các xử lý abc, xyz,… này vào trong khối finally, thì dù có đặt sau lệnh return, các xử lý đó vẫn được thực thi.

– Để minh họa cho điều này, chúng ta sẽ quay lại với hai ví dụ ② và ③ có sử dụng lệnh return:

Không có finally:
function myFunction() {
    try { 
        welcome("Welcome guest!");
    }
    catch(err) {
        document.getElementById("err").innerHTML = "Lỗi gọi hàm chưa được định nghĩa!!!";
        return;
    }
    
    // Vì đã return; ở trên, nên các xử lý phía dưới sẽ không được thực thi
    document.getElementById("message").innerHTML = "Minh Hoàng Blog | minhhn.com";
}
Try it »

Có finally:
function myFunction() {
    try { 
        welcome("Welcome guest!");
    }
    catch(err) {
        document.getElementById("err").innerHTML = "Lỗi gọi hàm chưa được định nghĩa!!!";
        return;
    }
    finally {
    	// Mặc dù đã return; nhưng khối lệnh finally vẫn được thực thi
    	document.getElementById("message").innerHTML = "Minh Hoàng Blog | minhhn.com";
	}
}
Try it »
3. Câu lệnh throw

3. Câu lệnh throw

– Câu lệnh throw cho phép bạn tạo ra một thông báo lỗi riêng tùy ý (custom error).

– Về mặt kỹ thuật bạn có thể ném ra một ngoại lệ (exception) (throw ra một error). Ngoại lệ có thể là một chuỗi JavaScript, một số, một boolean hoặc một đối tượng:

throw "Số quá lớn";	// throw ra một chuỗi
throw 500;          // throw ra môt số

– Nếu bạn sử dụng throw cùng với trycatch, bạn có thể kiểm soát luồng chương trình và tạo các thông báo lỗi tùy chỉnh:

Ví dụ:
function myFunction() {
    var message, x;
    message = document.getElementById("msg");
    message.innerHTML = "";
    x = document.getElementById("number").value;
    
    try { 
        if(x == "") throw "is empty";
        if(isNaN(x)) throw "is not a number";
        x = Number(x);
        if(x > 10) throw "is too high";
        if(x < 5) throw "is too low";
    }
    catch(err) {
        message.innerHTML = "Error: " + err + ".";
    }
    finally {
    	// Lúc nào thực hiện xong cũng reset giá trị input
        document.getElementById("number").value = "";
    }
}
Try it »
4. Đối tượng error

4. Đối tượng error

– JavaScript có một error object được xây dựng để cung cấp thông tin lỗi khi xảy ra lỗi.

– Error object có hai thuộc tính hữu ích là: namemessage.

Property Description
name Thiết lập (set) hoặc return tên của lỗi (error name).
message Thiết lập (set) hoặc return một chuỗi thông báo lỗi (error message).
Ví dụ:
try {
	blogMinhHoang;	// error, variable is not defined!
}
catch(err) {
	document.getElementById("demo").innerHTML = 
    "err: " + err + 
    "err.name: " + err.name + 
    "err.message: " + err.message;
}
Try it »

★ Giá trị của thuộc tính error name

Thuộc tính error name có 6 giá trị khác nhau có thể được return:

Error Name Description
EvalError Đã xảy ra lỗi trong hàm eval().
RangeError Đã xảy ra lỗi một số “ngoài phạm vi”.
ReferenceError Đã xảy ra lỗi tham chiếu không hợp lệ.
SyntaxError Đã xảy ra lỗi cú pháp.
TypeError Đã xảy ra lỗi type.
URIError Đã xảy ra lỗi trong encodeURI().
#1. Eval Error

EvalError chỉ ra một lỗi trong hàm eval().

Các phiên bản JavaScript mới không ném (throw) ra bất kỳ EvalError nào. Sử dụng SyntaxError để thay thế.

#2. Range Error

Một lỗi RangeError được ném (throw) ra nếu bạn sử dụng một số nằm ngoài phạm vi giá trị hợp lệ.

Ví dụ: Bạn không thể set một số có tổng các chữ số là 500.

Ví dụ:
var num = 1;
try {
    num.toPrecision(500);	// Một số không thể có 500 chữ số có nghĩa
}
catch(err) {
    document.getElementById("demo").innerHTML = err.name;
}
Try it »

Xem thêm về hàm toPrecision() ở bài viết: Thuộc tính và phương thức của đối tượng JavaScript Number.

#3. Reference Error

Một lỗi ReferenceError được ném (throw) ra nếu bạn sử dụng (tham chiếu) một biến chưa được khai báo:

Ví dụ:
var x;
try {
    x = y + 1;	// y không thể được tham chiếu (không thể được sử dụng)
}
catch(err) {
    document.getElementById("demo").innerHTML = err.name;
}
Try it »

#4. Syntax Error

Một lỗi SyntaxError được ném (throw) ra nếu bạn cố gắng evaluate code với một cú pháp lỗi:

Ví dụ:
try {
    eval("alert('Hello)");	// Lỗi vì thiếu dấu nháy '
}
catch(err) {
    document.getElementById("demo").innerHTML = err.name;
}
Try it »

#5. Type Error

Một lỗi TypeError được ném (throw) ra nếu bạn sử dụng giá trị nằm ngoài phạm vi của type được mong đợi:

Ví dụ:
var num = 1;
try {
    num.toUpperCase();	// Không thể chuyển đổi một số thành chữ hoa
}
catch(err) {
    document.getElementById("demo").innerHTML = err.name;
}
Try it »

#6. URI (Uniform Resource Identifier) Error

Một lỗi URIError được ném (throw) ra nếu bạn sử dụng các ký tự không hợp lệ trong hàm URI:

Ví dụ:
try {
    decodeURI("%%%");	// Không thể URI giải mã (decode) các ký tự phần trăm
}
catch(err) {
    document.getElementById("demo").innerHTML = err.name;
}
Try it »

★ Bắt lỗi trong catch block

Trường hợp các xử lý ở khối try có thể phát sinh lỗi mà bạn dự đoán được thì trong khối catch có thể sử dụng câu lệnh if xử lý riêng cho từng lỗi.

Ví dụ:
try {
    // các lệnh thực thi có thể phát sinh lỗi
    // ...
}
catch (e) {
    if (e instanceof TypeError) {
        // các lệnh handle TypeError exception
    } else if (e instanceof RangeError) {
        // các lệnh handle RangeError exception
    } else if (e instanceof EvalError) {
        // các lệnh handle EvalError exception
    } else {
       // Xử lý ngoại lệ chung mà không dự đoán được
       logMyErrors(e); // pass exception object to error handler
    }
}
5. Khối lệnh try/catch/finally có thể được lồng (nested) vào nhau

5. Khối lệnh try/catch/finally có thể được lồng (nested) vào nhau

– Đầu tiên, hãy xem điều gì xảy ra với ví dụ bên dưới:

Ví dụ ④:
try {
	try {
		throw new Error('oops');
	}
	finally {
		console.log('finally');
	}
}
catch (ex) {
	console.error('outer', ex.message);
}

// Output:
// "finally"
// "outer" "oops"
Try it »

– Bây giờ, chúng ta sẽ thêm một khối catch vào bên trong khối try-block inner:

Ví dụ ⑤:
try {
	try {
		throw new Error('oops');
	}
	catch (ex) {
		console.error('inner', ex.message);
	}
	finally {
		console.log('finally');
	}
}
catch (ex) {
	console.error('outer', ex.message);
}

// Output:
// "inner" "oops"
// "finally
Try it »

– Và bây giờ, sẽ ném lại lỗi throw (re-throw error) ở khối catch vừa thêm vào:

Ví dụ ⑥:
try {
	try {
		throw new Error('oops');
	}
	catch (ex) {
		console.error('inner', ex.message);
        throw ex;
	}
	finally {
		console.log('finally');
	}
}
catch (ex) {
	console.error('outer', ex.message);
}

// Output:
// "inner" "oops"
// "finally"
// "outer" "oops"
Try it »

Như vậy, qua 3 ví dụ ④, ⑤, ⑥ chúng ta thấy:

  • Ngoại lệ (exception) chỉ được bắt (catch) một lần bởi khối catch bao quanh gần nhất trừ khi nó được ném lại (re-throw).
  • Tất nhiên, bất kỳ ngoại lệ mới (new exception) nào được raised lên bên trong khối “inner” (vì code trong khối catch có thể throw ra một vài lỗi j đó) sẽ bị chặn bởi khối “outer”.

Trường hợp return từ finally block

– Ở ví dụ ⑥, chúng ta sẽ thêm lệnh return; vào finally block thì kết quả sẽ thế nào?

– Nếu finally block trả về một giá trị, thì giá trị này sẽ trở thành giá trị trả về của toàn bộ quá trình try-catch-finally, bất kể có bất kỳ câu lệnh return nào trong khối try và catch. Điều này bao gồm các exception được ném vào bên trong khối catch.

Ví dụ:
(function() {
	try {
		try {
			throw new Error('oops');
		}
		catch (ex) {
			console.error('inner', ex.message);
			throw ex;
		}
		finally {
			console.log('finally');
			return;
		}
	}
	catch (ex) {
		console.error('outer', ex.message);
	}
})();

// Kết quả output:
// "inner" "oops"
// "finally"
Try it »
Cảm ơn bạn đã theo dõi. Đừng ngần ngại hãy cùng thảo luận với chúng tôi!


Giới thiệu

Minh Hoàng

Xin chào, tôi là Hoàng Ngọc Minh, hiện đang làm BrSE, tại công ty Toyota, Nhật Bản. Những gì tôi viết trên blog này là những trải nghiệm thực tế tôi đã đúc rút ra được trong cuộc sống, quá trình học tập và làm việc. Các bài viết được biên tập một cách chi tiết, linh hoạt để giúp người đọc có thể tiếp cận một cách dễ dàng nhất. Hi vọng nó sẽ có ích hoặc mang lại một góc nhìn khác cho bạn[...]

Bình luận của bạn

avatar

Xử lý lỗi với try/catch/finally trong JavaScript

by Minh Hoàng Time to read: 13 min
0