JavaScript
Error Handling
try...catch...finally — ดักจับและจัดการ Error
try...catch...finally จัดการ error ไม่ให้โปรแกรมตาย — catch เพื่อรับมือ, finally เพื่อ cleanup, และ re-throw เมื่อจำเป็น
ปัญหาที่ try...catch...finally แก้ไข
จนถึงตอนนี้เราได้เรียนรู้ว่า Error — ทั้ง SyntaxError, TypeError, ReferenceError, RangeError — ทำให้โปรแกรมหยุดทำงานทันทีที่เกิด Error `throw` ก็เช่นกัน — มันหยุดโปรแกรมและส่ง Error object ขึ้น call stack ในโลกของการเขียนโปรแกรมจริง เรา**ไม่อยากให้โปรแกรมหยุดกลางคัน** โดยเฉพาะแอปที่ให้คนอื่นใช้ — ลองนึกภาพเว็บไซต์ที่เด้ง (crash) ทุกครั้งที่มีคนกรอกข้อมูลผิด format `try...catch...finally` คือกลไกของ JavaScript ที่ให้เรา: 1. **"ลอง"** (try) รันโค้ดที่อาจเกิด Error 2. **"ดักจับ"** (catch) Error เมื่อมันเกิดขึ้น — แล้วจัดการอย่างเหมาะสม 3. **"สุดท้าย"** (finally) รันโค้ดทำความสะอาด — ไม่ว่า Error จะเกิดหรือไม่ พูดง่าย ๆ: แทนที่จะปล่อยให้โปรแกรมหยุดตาย — เรามีแผนสำรองเมื่อเกิดปัญหา
เมื่อไม่มี try...catch โปรแกรมหยุดกลางคัน — เมื่อมี try...catch โปรแกรมจัดการ Error และทำงานต่อได้
// ❌ ไม่มี try...catch — Error ทำให้โปรแกรมหยุด
console.log("เริ่มโปรแกรม");
// สมมติ userInput มาจากผู้ใช้ — อาจไม่ใช่ JSON
let userInput = "แค่ข้อความธรรมดา ไม่ใช่ JSON";
let data = JSON.parse(userInput);
// ❌ SyntaxError: Unexpected token 'แ', ... is not valid JSON
// โปรแกรมหยุดตรงนี้ — บรรทัดล่างไม่ทำงาน
console.log("จบโปรแกรม"); // ❌ ไม่มีทางมาถึงบรรทัดนี้
// ===========================================
// ✅ มี try...catch — จัดการ Error แล้วโปรแกรมทำงานต่อ
console.log("เริ่มโปรแกรม");
try {
let userInput2 = "แค่ข้อความธรรมดา ไม่ใช่ JSON";
let data2 = JSON.parse(userInput2);
console.log("ข้อมูล:", data2);
} catch (err) {
console.log("❌ ข้อมูลไม่ถูกต้อง:", err.message);
console.log("ใช้ค่า default แทน");
}
console.log("จบโปรแกรม"); // ✅ ทำงานถึงบรรทัดนี้- ถ้าไม่มี `try...catch` และเกิด Error — โปรแกรมหยุดทันที โค้ดถัดไปทั้งหมดใน scope นั้นจะไม่ทำงาน
- `try...catch` เปลี่ยนโปรแกรมจาก **"หยุดตาย"** เป็น **"มีแผนสำรอง"** — Error ถูกดักจับและจัดการโดยไม่หยุดโปรแกรม
- ในแอปจริง: ผู้ใช้กรอกข้อมูลผิด format → เราแสดงข้อความแจ้งเตือน แทนที่จะให้แอปเด้ง
- `try` — block สำหรับโค้ดที่**อาจเกิด Error**
- `catch` — block สำหรับ**จัดการ** Error เมื่อมันเกิดขึ้น
- `finally` — block สำหรับ**โค้ดที่ต้องทำงานเสมอ** ไม่ว่าจะเกิด Error หรือไม่
try...catch — ดักจับ Error ไม่ให้โปรแกรมหยุด
โครงสร้างพื้นฐานของ `try...catch` ประกอบด้วย 2 ส่วนหลัก: ``` try { // โค้ดที่อาจเกิด Error } catch (err) { // โค้ดจัดการ Error } ``` การทำงานมี 2 เส้นทาง: **ถ้าไม่มี Error เกิดขึ้นใน try**: โค้ดใน try ทำงานจนจบ — แล้ว**ข้าม catch ไปเลย** — โปรแกรมทำงานต่อตามปกติ **ถ้ามี Error เกิดขึ้นใน try**: JavaScript จะ**หยุดรันโค้ดที่เหลือใน try ทันที** แล้วกระโดดไปที่ catch block — หลังจาก catch ทำงานเสร็จ โค้ดที่อยู่หลัง try...catch จะทำงานต่อ จุดสำคัญ: `try...catch` จับ Error ที่เกิดใน try block เท่านั้น — Error ที่เกิดนอก try (หรือเกิดใน function ที่เรียกจาก try แต่ function นั้นไม่มี try...catch ของตัวเอง) จะ**ไม่ถูกจับ**
เมื่อข้อมูลถูกต้อง catch จะถูกข้าม — เมื่อข้อมูลผิด catch จะทำงานและป้องกันไม่ให้โปรแกรมหยุด
function registerUser(age) {
try {
if (age < 0) {
throw new Error("อายุต้องมากกว่า 0");
}
if (age > 150) {
throw new Error("อายุเกินจริง — กรุณาตรวจสอบอีกครั้ง");
}
console.log("✅ ลงทะเบียนสำเร็จ — อายุ:", age);
} catch (err) {
console.log("❌ ลงทะเบียนไม่สำเร็จ:", err.message);
}
console.log("--- จบฟังก์ชัน ---");
}
// ข้อมูลถูกต้อง — catch ถูกข้าม
registerUser(25);
// output:
// ✅ ลงทะเบียนสำเร็จ — อายุ: 25
// --- จบฟังก์ชัน ---
// ข้อมูลผิด — catch ทำงาน
registerUser(-5);
// output:
// ❌ ลงทะเบียนไม่สำเร็จ: อายุต้องมากกว่า 0
// --- จบฟังก์ชัน ---
// ข้อมูลผิดอีกกรณี — catch ทำงาน
registerUser(200);
// output:
// ❌ ลงทะเบียนไม่สำเร็จ: อายุเกินจริง — กรุณาตรวจสอบอีกครั้ง
// --- จบฟังก์ชัน ---- `try { ... }` — ใส่โค้ดที่อาจเกิด Error ไว้ในนี้
- `catch(err) { ... }` — ใส่โค้ดจัดการ Error ไว้ในนี้ `err` คือ Error object ที่ถูก throw มา
- **ถ้าไม่มี Error**: โค้ดใน try ทำงานครบ → ข้าม catch → โปรแกรมทำงานต่อ
- **ถ้ามี Error**: โค้ดที่เหลือใน try หยุดทันที → กระโดดไป catch → จัดการ Error → โปรแกรมทำงานต่อ
- `try...catch` จับ Error ที่เกิด**ภายใน** try block เท่านั้น
- หลังจาก catch ทำงานเสร็จ — โค้ดที่อยู่หลัง `try...catch` จะทำงานต่อตามปกติ
catch(err) — รับ Error Object และอ่านรายละเอียด
`catch` สามารถรับ parameter ได้ 1 ตัว — เรามักตั้งชื่อว่า `err` หรือ `error` (จริง ๆ ชื่ออะไรก็ได้ แต่ตั้งให้สื่อความหมาย) parameter นี้คือ **Error object** ที่ถูก throw มา — มันมี `.name` และ `.message` เหมือนที่เราเรียนมาในบท Error Object ประโยชน์ของการอ่าน `.name` และ `.message` ใน catch: - **`.name`** — บอก**ประเภทของ error** — เราใช้แยกการจัดการได้ เช่น SyntaxError → แสดงข้อความหนึ่ง, TypeError → แสดงอีกข้อความหนึ่ง - **`.message`** — บอก**รายละเอียด** — ใช้แสดงให้ผู้ใช้รู้ว่าเกิดอะไรขึ้น หรือใช้ log ไว้ debug การเข้าถึง `.name` ใน catch มีประโยชน์มาก — เพราะ error ที่ถูก throw มาจากหลายที่อาจมี `.name` ต่างกัน
catch parameter คือ Error object — เข้าถึง .name และ .message ได้เหมือน Error object ทั่วไป
// Error ที่สร้างด้วย new Error() — .name เป็น "Error"
try {
throw new Error("สินค้าหมดสต็อก");
} catch (err) {
console.log("ชื่อ error:", err.name);
// output: "ชื่อ error: Error"
console.log("ข้อความ:", err.message);
// output: "ข้อความ: สินค้าหมดสต็อก"
}
// Error ที่เกิดจาก JavaScript เอง — .name ต่างกันตามประเภท
try {
JSON.parse("ไม่ใช่ JSON");
} catch (err) {
console.log("ชื่อ error:", err.name);
// output: "ชื่อ error: SyntaxError"
console.log("ข้อความ:", err.message);
// output: "ข้อความ: Unexpected token 'ไ', ... is not valid JSON"
}
// TypeError ที่เกิดจาก null
try {
let user = null;
console.log(user.name);
} catch (err) {
console.log("ชื่อ error:", err.name);
// output: "ชื่อ error: TypeError"
console.log("ข้อความ:", err.message);
// output: "ข้อความ: Cannot read properties of null (reading 'name')"
}ใช้ err.name เพื่อจัดการ Error แต่ละประเภทต่างกัน — SyntaxError กับ TypeError อาจต้องการการตอบสนองที่ต่างกัน
function parseUserData(input) {
try {
let clean = input.trim();
let data = JSON.parse(clean);
console.log("✅ ข้อมูลที่ได้:", data);
return data;
} catch (err) {
// แยกการจัดการตามประเภท error
if (err.name === "SyntaxError") {
console.log("❌ ข้อมูลที่ส่งมาไม่ใช่ JSON ที่ถูกต้อง");
console.log(" รายละเอียด:", err.message);
return null;
} else if (err.name === "TypeError") {
console.log("❌ เกิดข้อผิดพลาดเกี่ยวกับ type ของข้อมูล");
console.log(" รายละเอียด:", err.message);
return null;
} else {
// Error ประเภทอื่นที่ไม่ได้คาดการณ์ไว้
console.log("❌ เกิด Error ที่ไม่คาดคิด:", err.name, "-", err.message);
return null;
}
}
}
// ข้อมูลถูกต้อง
parseUserData('{ "name": "สมชาย", "age": 30 }');
// output:
// ✅ ข้อมูลที่ได้: { name: "สมชาย", age: 30 }
// ข้อมูลไม่ใช่ JSON — SyntaxError
parseUserData("แค่ข้อความธรรมดา ไม่ใช่ JSON");
// output:
// ❌ ข้อมูลที่ส่งมาไม่ใช่ JSON ที่ถูกต้อง
// รายละเอียด: Unexpected token 'แ', ... is not valid JSON
// null ทำให้เกิด TypeError — input.trim() จะ throw เมื่อ input เป็น null
parseUserData(null);
// output:
// ❌ เกิดข้อผิดพลาดเกี่ยวกับ type ของข้อมูล
// รายละเอียด: Cannot read properties of null (reading 'trim')- `catch(err)` — `err` คือ Error object ที่ถูก throw มา — ตั้งชื่ออะไรก็ได้ (นิยมใช้ `err` หรือ `error`)
- `err.name` — string บอกประเภทของ error: `"SyntaxError"`, `"TypeError"`, `"ReferenceError"`, `"RangeError"`, `"Error"`
- `err.message` — string บอกรายละเอียดของ error — มนุษย์อ่านเข้าใจ
- ใช้ `err.name` เพื่อ**แยกการจัดการ**ตามประเภท: ถ้าเป็น SyntaxError → แสดงข้อความหนึ่ง, TypeError → แสดงอีกข้อความหนึ่ง, อื่น ๆ → จัดการแบบ default
- `catch` โดยไม่ระบุ parameter ก็ได้ (`catch { ... }`) — JavaScript สมัยใหม่รองรับ — แต่แนะนำให้ระบุ `(err)` เพื่ออ่านรายละเอียด error
- `console.log(err)` ใช้ได้ — JavaScript จะแสดงทั้ง `.name`, `.message`, และ stack trace
finally — โค้ดที่ต้องทำงานเสมอไม่ว่าเกิด Error หรือไม่
`finally` คือ block ที่เพิ่มต่อท้าย `try...catch` — โค้ดใน finally จะถูกทำงาน**เสมอ** ไม่ว่าจะเกิดอะไรขึ้น: - ไม่มี Error เลย → finally ทำงาน - มี Error และ catch จัดการแล้ว → finally ทำงาน - มี Error แต่ catch จัดการไม่ได้ → finally ทำงานก่อน error ถูกส่งต่อ - มี `return` ใน try หรือ catch → finally **ทำงานก่อน return จริง** finally ถูกออกแบบมาสำหรับ **cleanup** (การทำความสะอาด) — คือการคืนทรัพยากรหรือรีเซ็ตสถานะกลับมาให้เรียบร้อย ไม่ว่าโปรแกรมจะทำงานสำเร็จหรือล้มเหลว ตัวอย่างสถานการณ์ที่ควรใช้ finally: - ปิดการเชื่อมต่อฐานข้อมูล - ยกเลิกการเชื่อมต่อ network - ซ่อน loading spinner - ล้าง temporary data - ปลดล็อก resource ที่ถูกจองไว้ **กฎทองของ finally**: ถ้ามีอะไรที่ต้องทำเสมอ ไม่ว่า success หรือ failure — ใส่ไว้ใน finally
นี่คือคุณสมบัติที่สำคัญที่สุดของ finally: มันทำงานแม้ try หรือ catch มี return — JavaScript จะจำค่าที่จะ return ไว้ แล้วรัน finally ก่อน แล้วค่อย return
function processData(data) {
console.log("เริ่มประมวลผล...");
try {
if (!data) {
throw new Error("ไม่มีข้อมูลให้ประมวลผล");
}
console.log("กำลังประมวลผล:", data);
return "✅ เสร็จสิ้น"; // return ใน try
} catch (err) {
console.log("เกิดปัญหา:", err.message);
return "❌ ล้มเหลว"; // return ใน catch
} finally {
// finally ทำงานเสมอ — แม้ try หรือ catch มี return!
console.log("🧹 cleanup — ปิดการเชื่อมต่อ");
}
}
console.log("ผลลัพธ์:", processData("ข้อมูลสำคัญ"));
// output:
// เริ่มประมวลผล...
// กำลังประมวลผล: ข้อมูลสำคัญ
// 🧹 cleanup — ปิดการเชื่อมต่อ
// ผลลัพธ์: ✅ เสร็จสิ้น
// ^ สังเกต: finally ทำงานก่อน return "✅ เสร็จสิ้น"
console.log("---");
console.log("ผลลัพธ์:", processData(null));
// output:
// เริ่มประมวลผล...
// เกิดปัญหา: ไม่มีข้อมูลให้ประมวลผล
// 🧹 cleanup — ปิดการเชื่อมต่อ
// ผลลัพธ์: ❌ ล้มเหลว
// ^ สังเกต: finally ทำงานก่อน return "❌ ล้มเหลว" เช่นกันfinally ใช้เดี่ยว ๆ ได้ — เมื่ออยากให้ cleanup ทำงานแต่ไม่ต้องการดักจับ error (error จะถูกส่งต่อไปให้ caller จัดการ)
function readFile(filename) {
console.log("เปิดไฟล์:", filename);
try {
// สมมติว่าโค้ดตรงนี้อ่านไฟล์ — อาจเกิด Error
if (filename === "") {
throw new Error("ชื่อไฟล์ว่างเปล่า");
}
console.log("อ่านไฟล์สำเร็จ");
return "เนื้อหาของไฟล์";
} finally {
// ไม่ว่าไฟล์จะเปิดสำเร็จหรือไม่ — ต้องปิดให้เรียบร้อย
console.log("ปิดไฟล์:", filename);
}
// ^ try...finally ไม่มี catch — error จะถูกส่งต่อไปให้ caller
}
console.log("ผลลัพธ์:", readFile("data.txt"));
// output:
// เปิดไฟล์: data.txt
// อ่านไฟล์สำเร็จ
// ปิดไฟล์: data.txt
// ผลลัพธ์: เนื้อหาของไฟล์
// ถ้าเกิด Error — finally ยังทำงาน แต่ error หลุดไป
try {
readFile("");
} catch (err) {
console.log("Caller จัดการ error:", err.message);
}
// output:
// เปิดไฟล์:
// ปิดไฟล์: <-- finally ทำงานก่อน error หลุด
// Caller จัดการ error: ชื่อไฟล์ว่างเปล่า
// ^ error ถูกส่งไปให้ caller เพราะไม่มี catch ใน readFile- `finally { ... }` — โค้ดในนี้จะทำงาน**เสมอ 100%** ไม่ว่าเกิดอะไรขึ้น
- finally ทำงานแม้ try หรือ catch มี `return` — JavaScript จำค่าที่จะ return ไว้ → รัน finally → return ค่านั้น
- finally ทำงานแม้ error หลุดจาก catch (re-throw) — cleanup ยังทำงานก่อน error ถูกส่งต่อไป
- ใช้ finally สำหรับ cleanup: ปิดการเชื่อมต่อ, ลบ temporary data, ซ่อน loading UI, ปลดล็อก resource
- `try...finally` (ไม่มี catch) ใช้ได้ — cleanup ยังทำงาน แต่ error จะถูกส่งต่อไปให้ caller จัดการ
throw ซ้ำใน catch — ส่ง Error ต่อไปเมื่อจัดการเองไม่ได้
บางครั้งเราไม่ได้อยาก "จัดการ" error ใน catch จริง ๆ — เราอยากทำอะไรบางอย่างก่อน (เช่น log error) แล้ว**ส่ง error ต่อไป**ให้โค้ดที่เรียกใช้จัดการต่อ การ `throw` ใน catch เรียกว่า **re-throw** — error จะถูกส่งขึ้น call stack ไปให้ `try...catch` ที่อยู่สูงกว่าจัดการ Re-throw มีประโยชน์เมื่อ: - **Log ก่อนส่งต่อ** — บันทึก error ไว้ debug แล้วปล่อยให้ caller ตัดสินใจว่าจะทำอะไรต่อ - **เพิ่มข้อมูลให้ error** — สร้าง Error ใหม่ที่มีข้อมูลมากขึ้น (wrap error) - **จัดการบางกรณี ปล่อยกรณีอื่น** — เช่น จัดการ SyntaxError เอง แต่ re-throw Error ประเภทอื่น **ข้อสำคัญ**: การ re-throw โดยไม่เพิ่มข้อมูลเลย (`throw err;` เฉย ๆ) มีประโยชน์น้อย — อย่างน้อยควร log ไว้ก่อน หรือเพิ่ม context ให้ error message และจำไว้ว่า: finally (ถ้ามี) จะทำงาน**ก่อน** error ถูก re-throw ขึ้นไป — cleanup เสมอมาก่อน
ฟังก์ชัน readConfig log error ไว้ก่อนแล้ว re-throw — ฟังก์ชัน startApp ที่เรียกใช้คอยจัดการ error นั้นอีกที
// ฟังก์ชันระดับล่าง — อ่าน config จาก JSON string
function readConfig(configJson) {
try {
let config = JSON.parse(configJson);
return config;
} catch (err) {
// Log ไว้ก่อน — เผื่อ debug ทีหลัง
console.log("[WARN] อ่าน config ไม่สำเร็จ:", err.message);
// Re-throw — ให้คนเรียก (caller) จัดการต่อ
throw err;
}
}
// ฟังก์ชันระดับบน — เรียก readConfig และจัดการ error
function startApp(configJson) {
try {
let config = readConfig(configJson);
console.log("🚀 App เริ่มทำงานด้วย config:", config);
} catch (err) {
console.log("❌ ไม่สามารถเริ่ม App ได้");
console.log(" ใช้ค่า default แทน");
// ในแอปจริง อาจโหลด config default หรือแจ้งเตือนผู้ใช้
}
}
// ทดสอบด้วย JSON ที่ถูกต้อง
startApp('{ "port": 8080, "debug": true }');
// output:
// 🚀 App เริ่มทำงานด้วย config: { port: 8080, debug: true }
console.log("==========");
// ทดสอบด้วยข้อมูลที่ผิดพลาด
startApp("ไม่ใช่ JSON จ้า");
// output:
// [WARN] อ่าน config ไม่สำเร็จ: Unexpected token 'ไ', ... is not valid JSON
// ❌ ไม่สามารถเริ่ม App ได้
// ใช้ค่า default แทนใช้ err.name ตรวจสอบ — ถ้าเป็น error ที่เราจัดการได้ก็จัดการ ถ้าไม่ใช่ก็ re-throw ให้ caller จัดการ
function validateAndParse(input) {
try {
if (typeof input !== "string") {
throw new TypeError("input ต้องเป็น string เท่านั้น");
}
let data = JSON.parse(input);
return data;
} catch (err) {
if (err.name === "SyntaxError") {
// JSON parse error — เราจัดการเองได้
console.log("❌ JSON ไม่ถูกต้อง — ใช้ค่า default");
return { error: "invalid_json", data: null };
}
// Error ประเภทอื่น (เช่น TypeError) — เราไม่รู้จะจัดการยังไง
// Re-throw ให้ caller จัดการ
throw err;
}
}
// JSON parse error — จัดการเอง
console.log(validateAndParse("bad json"));
// output:
// ❌ JSON ไม่ถูกต้อง — ใช้ค่า default
// { error: "invalid_json", data: null }
// TypeError — re-throw ให้ caller
try {
validateAndParse(123);
} catch (err) {
console.log("Caller ได้รับ error:", err.name, "-", err.message);
}
// output:
// Caller ได้รับ error: TypeError - input ต้องเป็น string เท่านั้น- **Re-throw** = ใช้ `throw err` ใน catch — ส่ง error ต่อไปให้ caller (ฟังก์ชันที่เรียกใช้) จัดการ
- ใช้เมื่ออยาก**ทำอะไรก่อน**ส่งต่อ: log error, เพิ่ม context, wrap เป็น error ใหม่
- ใช้ `err.name` เลือกว่าจะจัดการเองหรือ re-throw — จัดการเฉพาะ error ที่เรารู้วิธีรับมือ
- ถ้ามี `finally` — finally จะทำงาน**ก่อน** error ถูก re-throw ขึ้นไป
- ไม่ควร re-throw โดยไม่เพิ่มข้อมูลหรือไม่ log — อย่างน้อยควร `console.log` ไว้ก่อน
- การ re-throw โดยไม่แก้ไข error object (`throw err;`) รักษา stack trace เดิม — ดีกว่าการ `throw new Error(...)` ที่ทำให้เสีย stack trace เก่า
ข้อควรระวังและความเข้าใจผิดที่พบบ่อย
`try...catch` เป็นกลไกที่ทรงพลัง — แต่ถ้าใช้ผิดวิธีอาจทำให้หาบัคยากกว่าเดิมหรือกลายเป็นช่องโหว่ที่ซ่อน error ไว้เงียบ ๆ นี่คือข้อควรระวังที่มือใหม่มักพลาด — และวิธีใช้ try...catch อย่างถูกต้อง:
| ข้อควรระวัง | ปัญหาที่เกิด | วิธีที่ถูกต้อง |
|---|---|---|
| catch ว่างเปล่า — `catch(err) {}` โดยไม่ทำอะไรเลย | Error ถูก **"กลืน" (swallow)** หายไปเงียบ ๆ — โปรแกรมทำงานต่อแต่ logic อาจผิด — หาบัคยากมากเพราะไม่มีร่องรอย error เลย | อย่างน้อยควร `console.log(err.message)` — หรือแสดงข้อความแจ้งเตือนผู้ใช้ |
| catch แล้วไม่ดู `err.name` — จัดการทุก error เหมือนกันหมด | SyntaxError กับ TypeError ควรจัดการต่างกัน — การจัดการเหมาเข่งอาจซ่อนปัญหาใหญ่ | ใช้ `if (err.name === "...")` แยกการจัดการตามประเภท error |
| ลืม `finally` สำหรับ cleanup — เปิด connection ใน try แต่ไม่ปิด | Connection หรือ resource ค้าง — หน่วยความจำรั่ว (memory leak) — แอปทำงานไปเรื่อย ๆ จนทรัพยากรหมด | เปิดใน try → ปิดใน finally — `try { open() } finally { close() }` |
| ใส่โค้ดใน `try` มากเกินไป — รวมโค้ดที่อาจ error กับโค้ดที่ error ไม่ได้ไว้ใน try เดียวกัน | เวลา error เกิด — ไม่รู้ว่าเกิดจากส่วนไหน — debug ยาก | try block ควรเล็กและโฟกัส — ครอบเฉพาะโค้ดที่อาจเกิด error จริง ๆ |
| ใช้ `try...catch` แทน `if` — ใช้ try...catch ควบคุม flow ปกติของโปรแกรม | Performance ตก — try...catch มี overhead มากกว่า if — และทำให้โค้ดอ่านยาก | try...catch ใช้สำหรับกรณี**คาดไม่ถึง** — ใช้ `if` ตรวจสอบเงื่อนไขปกติ |
| finally มี `return` — return ใน finally ทับค่า return จาก try/catch | ค่าที่ควร return จาก try หรือ catch หายไป — ได้ค่าจาก finally แทนโดยไม่ตั้งใจ | ห้ามใส่ `return` ใน finally — ใช้ finally สำหรับ side effect (cleanup) เท่านั้น |
| คิดว่า `try...catch` จับ error ได้ทุกประเภท | `try...catch` จับเฉพาะ error ที่เกิด**ตอน runtime** เท่านั้น — SyntaxError ที่เกิดตอน parse (ก่อนรัน) จับไม่ได้ | SyntaxError ตอน parse เกิดก่อนโค้ดรัน — try...catch ช่วยไม่ได้ ต้องแก้ syntax โดยตรง |
| catch ไม่ระบุ parameter ทั้งที่อยากรู้ว่า error อะไร | รู้แค่ว่า error เกิด — แต่ไม่รู้ว่า error ประเภทไหน ข้อความอะไร — จัดการต่อไม่ได้ | ระบุ `catch(err)` เสมอ — แม้จะแค่ `console.log(err)` ก็ยังดีกว่าไม่รู้อะไรเลย |
สรุป — try...catch...finally
- **`try`** — block สำหรับโค้ดที่อาจเกิด Error — JavaScript จะลองรันโค้ดในนี้ก่อน
- **`catch(err)`** — block สำหรับดักจับและจัดการ Error — `err` คือ Error object ที่มี `.name` และ `.message` — ใช้ `err.name` แยกประเภท error เพื่อจัดการต่างกัน
- **`finally`** — block สำหรับโค้ดที่ต้องทำงาน**เสมอ** — ไม่ว่า error จะเกิดหรือไม่, catch จะจัดการหรือไม่, try/catch จะมี return หรือไม่ — ใช้สำหรับ cleanup (ปิด connection, ล้างข้อมูลชั่วคราว)
- **Re-throw** (`throw err` ใน catch) — ส่ง error ต่อไปให้ caller จัดการ — ใช้เมื่ออยากทำบางอย่างก่อน (เช่น log) แต่ไม่สามารถจัดการ error ได้สมบูรณ์
- **`try...finally` (ไม่มี catch)** — ใช้เมื่อต้องการ cleanup แต่ปล่อยให้ caller จัดการ error
- **`try...catch` (ไม่มี finally)** — ใช้เมื่อดักจับและจัดการ error โดยไม่ต้องทำ cleanup
- **`try...catch...finally` ครบทั้งสาม** — ใช้เมื่อต้องการทั้งดักจับ error และทำ cleanup — รูปแบบที่สมบูรณ์ที่สุด
- **กฎสำคัญ**: อย่าใช้ catch ว่างเปล่า, อย่าใช้ try...catch แทน if, เปิด resource ใน try ต้องปิดใน finally, และระบุ `catch(err)` เสมอเพื่ออ่านรายละเอียด error
| รูปแบบ | เมื่อไหร่ควรใช้ | ตัวอย่างสถานการณ์ |
|---|---|---|
| `try { } catch(err) { }` | ต้องการดักจับและจัดการ Error — ไม่มี cleanup | ตรวจสอบ user input, parse JSON, เรียก API |
| `try { } finally { }` | ต้องการ cleanup แต่ให้ caller จัดการ Error | เปิดไฟล์แล้วต้องปิด — แต่ให้ caller จัดการ error จากการอ่านไฟล์ |
| `try { } catch(err) { } finally { }` | ต้องการทั้งจัดการ Error และ cleanup | เปิด connection → ใช้ข้อมูล (อาจ error) → ปิด connection — ทั้งหมดนี้ต้องเกิดขึ้น |
| `try { } catch(err) { throw err }` + finally | อยาก log หรือเพิ่มข้อมูลให้ error แล้วส่งต่อ — บวกกับ cleanup | Log error ก่อนส่งต่อไป monitor — แล้วปิด connection ใน finally |