JavaScript
Error Handling
throw — โยน Error เพื่อหยุดโปรแกรมและส่งสัญญาณ
throw หยุดการทำงานทันทีและส่ง Error object ขึ้น call stack — เรียนรู้เมื่อไหร่ควร throw และเลือก error type ยังไง
throw คืออะไร
`throw` เป็น keyword ใน JavaScript ที่ใช้**หยุดการทำงานของโปรแกรมทันที** และ**ส่ง Error object (หรือค่าอะไรก็ได้) ขึ้นไปยัง call stack** เพื่อบอกว่าเกิดข้อผิดพลาดร้ายแรงจนทำงานต่อไม่ได้ ความแตกต่างสำคัญระหว่าง `new Error()` กับ `throw`: - `new Error("ข้อความ")` — **สร้าง Error object** เฉย ๆ (เหมือนสร้าง object ทั่วไป) — โค้ดบรรทัดถัดไปยังทำงานต่อ - `throw new Error("ข้อความ")` — **สร้าง Error object แล้วโยนมันออกไป** — หยุดการทำงานทันที โค้ดบรรทัดถัดไปใน scope เดียวกันจะ**ไม่ทำงาน** คิดง่าย ๆ: `new Error()` คือการ**เขียนป้ายเตือน** — แค่เตรียมข้อความไว้ แต่ยังไม่มีใครเห็น `throw` คือการ**ยกป้ายนั้นขึ้นมาแล้วประกาศให้ทุกคนหยุด** — ทุกอย่างหยุดทันทีและทุกส่วนของโปรแกรมรู้ว่ามีปัญหา `throw` เป็นกลไกเดียวกับที่ JavaScript ใช้ภายในของมันเองเวลาเจอ TypeError, ReferenceError, RangeError — ความจริงแล้ว error พวกนั้นทั้งหมดถูก JavaScript engine **throw** ขึ้นมาให้เราเห็น
new Error() เป็นแค่การสร้าง object — โค้ดทำงานต่อ throw new Error() หยุดการทำงานทันที โค้ดหลัง throw ไม่ถูก execute
// ❌ new Error() เฉย ๆ — โปรแกรมไม่หยุด
console.log("ก่อนสร้าง Error");
const myError = new Error("มีปัญหาเกิดขึ้น");
console.log(myError.message); // "มีปัญหาเกิดขึ้น"
console.log("หลังสร้าง Error — ยังทำงานต่อได้!");
// output:
// "ก่อนสร้าง Error"
// "มีปัญหาเกิดขึ้น"
// "หลังสร้าง Error — ยังทำงานต่อได้!"
// ^ new Error() เป็นแค่การสร้าง object — โค้ดทุกบรรทัดทำงานครบ
// ❌ throw new Error() — หยุดโปรแกรมทันที
console.log("ก่อน throw");
throw new Error("พบข้อผิดพลาดร้ายแรง!");
console.log("บรรทัดนี้จะไม่ทำงาน"); // ❌ ไม่ถูก execute
// output:
// "ก่อน throw"
// Error: พบข้อผิดพลาดร้ายแรง!
// ^ throw หยุดการทำงานที่บรรทัด throw ทันที
// console.log หลัง throw ไม่ถูก execute เลย- `throw` เป็น keyword ที่**หยุดการทำงานทันที** และส่ง error ขึ้น call stack
- `new Error()` สร้าง**แค่ object** — ไม่หยุดโปรแกรม, `throw new Error()` สร้าง error **และหยุดโปรแกรม**
- JavaScript ใช้ `throw` ภายในของมันเองเวลาเจอ error ต่าง ๆ — TypeError, ReferenceError, RangeError ล้วนถูก throw ขึ้นมา
- throw คือสัญญาณว่า "มีบางอย่างผิดพลาดจนทำงานต่อไม่ได้" — เหมือนเป็นปุ่มหยุดฉุกเฉิน
syntax ของ throw
syntax ของ `throw` ง่ายมาก: ``` throw expression; ``` `expression` คือค่าอะไรก็ได้ — `throw` สามารถโยน string, number, object, หรืออะไรก็ได้ทั้งนั้น **แต่ในทางปฏิบัติ ควรโยน Error object (หรือ subtype ของ Error) เท่านั้น** — เพราะ Error object มี `.name` และ `.message` ที่ช่วยบอกว่าเกิดอะไรขึ้น และมี **stack trace** ที่บอกว่า error เกิดที่บรรทัดไหน ผ่าน function อะไรมาบ้าง การโยน string หรือ number จะทำให้ข้อมูล error ไม่สมบูรณ์ — คนที่มารับ error (หรือ console) จะไม่รู้ว่า error ประเภทอะไร และไม่รู้ว่าเกิดที่บรรทัดไหน
throw รับ expression อะไรก็ได้ แต่การโยน Error object (หรือ subtype) เป็น best practice เพราะมี .name, .message, และ stack trace
// ❌ throw string — ไม่แนะนำ
throw "เกิดข้อผิดพลาด";
// ^ โยน string ธรรมดา — ไม่มี .name, .message, ไม่มี stack trace
// console แสดงแค่ string — ไม่รู้ว่า error ประเภทอะไร เกิดที่บรรทัดไหน
// ❌ throw number — ไม่แนะนำ
throw 404;
// ^ โยนตัวเลข — ยิ่งแย่กว่า string เพราะ number ไม่มีทางสื่อสารได้ว่าเกิดอะไรขึ้น
// ✅ throw Error object — แนะนำ
throw new Error("ไม่พบข้อมูลผู้ใช้");
// ^ Error object มี .name = "Error", .message = "ไม่พบข้อมูลผู้ใช้"
// และมี stack trace บอกว่าเกิดที่บรรทัดไหน ผ่าน function อะไรมาบ้าง
// ✅ throw Error subtype — ระบุประเภท error ได้ชัดเจน
throw new TypeError("price ต้องเป็นตัวเลขเท่านั้น");
// ^ TypeError บอกชัดว่าเป็น error เกี่ยวกับ type — ต่างจาก Error ทั่วไป- syntax: `throw expression;` — expression คือค่าอะไรก็ได้
- **Best practice**: โยน `Error` object หรือ subtype (`TypeError`, `RangeError`, ฯลฯ) — ไม่โยน string หรือ number เปล่า
- Error object มีข้อดี: `.name` บอกประเภท error, `.message` บอกรายละเอียด, **stack trace** บอกตำแหน่งที่เกิด error
- การโยน string/number ทำให้ข้อมูล error ไม่สมบูรณ์ — หาจุดที่เกิด error ยาก
เกิดอะไรขึ้นเมื่อ throw
เมื่อ JavaScript เจอ `throw` มันจะทำตามขั้นตอนต่อไปนี้: 1. **หยุดการทำงานที่บรรทัด `throw` ทันที** — โค้ดหลังจาก `throw` ใน scope เดียวกันจะไม่ถูก execute 2. **สร้าง exception** — ค่าที่อยู่หลัง `throw` จะกลายเป็น exception object ที่เดินทางขึ้น call stack 3. **เดินทางขึ้น call stack** — exception จะเดินทางย้อนกลับขึ้นไปตาม function ที่เรียกซ้อนกันมา (จากชั้นในสุด → ชั้นนอกสุด) จนกว่าจะเจอ `try-catch` ที่ดักจับมันไว้ 4. **ถ้าไม่มี `try-catch`** — โปรแกรมหยุดทำงานและแสดง error ใน console กระบวนการนี้เรียกว่า **"error propagation"** หรือ **"unwinding the stack"** — error เดินทางจากจุดที่เกิด ผ่านทุก function ที่เรียกมาตามลำดับ จนถึงจุดเริ่มต้นของโปรแกรม การเข้าใจ propagation ช่วยให้เรารู้ว่า **throw ไม่ได้หยุดแค่ function ปัจจุบัน — มันหยุดทุก function ใน call chain** จนกว่าจะมีคนดักจับ error ไว้
error ถูก throw ใน function ชั้นในสุด แล้วเดินทางขึ้นผ่านทุก function ที่เรียกมา — ทุก function ใน call chain หยุดทำงาน
function level3() {
console.log("level3: เริ่มทำงาน");
throw new Error("เกิดข้อผิดพลาดใน level3!");
console.log("level3: บรรทัดนี้ไม่ทำงาน"); // ❌ ไม่ถูก execute
}
function level2() {
console.log("level2: เริ่มทำงาน");
level3(); // level3() throw error — level2() ก็หยุดด้วย
console.log("level2: บรรทัดนี้ไม่ทำงาน"); // ❌ ไม่ถูก execute
}
function level1() {
console.log("level1: เริ่มทำงาน");
level2(); // level2() รับ error ต่อจาก level3() — level1() ก็หยุดด้วย
console.log("level1: บรรทัดนี้ไม่ทำงาน"); // ❌ ไม่ถูก execute
}
console.log("เริ่มโปรแกรม");
level1();
console.log("จบโปรแกรม — บรรทัดนี้ไม่ทำงาน"); // ❌ ไม่ถูก execute
// output:
// "เริ่มโปรแกรม"
// "level1: เริ่มทำงาน"
// "level2: เริ่มทำงาน"
// "level3: เริ่มทำงาน"
// Error: เกิดข้อผิดพลาดใน level3!
// at level3 (...)
// at level2 (...)
// at level1 (...)
// ^ error เดินทาง: level3 → level2 → level1 → global
// ทุก function ใน chain หยุดทำงาน — ไม่มีใครได้ทำงานต่อทุกบรรทัดก่อน throw ทำงานตามปกติ — แต่ทุกบรรทัดหลัง throw (ใน scope เดียวกัน) จะไม่ถูก execute
function processOrder(order) {
console.log("1. เริ่มประมวลผลคำสั่งซื้อ"); // ✅ ทำงาน
if (!order.items || order.items.length === 0) {
console.log("2. ตรวจพบว่าไม่มีรายการสินค้า"); // ✅ ทำงาน (ถ้าเงื่อนไขเป็นจริง)
throw new Error("คำสั่งซื้อต้องมีรายการสินค้าอย่างน้อย 1 รายการ"); // ❌ หยุดตรงนี้
console.log("3. บรรทัดนี้ไม่ทำงาน"); // ❌ ไม่ถูก execute
}
console.log("4. คำนวณราคารวม"); // ✅ ทำงาน (ถ้าไม่ throw)
return order.items.length;
}
// กรณีมีสินค้า — ไม่ throw
console.log(processOrder({ items: ["A", "B"] }));
// output:
// "1. เริ่มประมวลผลคำสั่งซื้อ"
// "4. คำนวณราคารวม"
// 2
// กรณีไม่มีสินค้า — throw
console.log(processOrder({ items: [] }));
// output:
// "1. เริ่มประมวลผลคำสั่งซื้อ"
// "2. ตรวจพบว่าไม่มีรายการสินค้า"
// Error: คำสั่งซื้อต้องมีรายการสินค้าอย่างน้อย 1 รายการ
// ^ บรรทัดที่ 3 และ 4 ไม่ทำงาน — throw หยุดทุกอย่าง- **throw หยุดการทำงานที่บรรทัด throw ทันที** — โค้ดหลัง throw ใน scope เดียวกันจะไม่ถูก execute
- **โค้ดก่อน throw ทำงานตามปกติ** — ทุกอย่างก่อนถึงบรรทัด throw ทำงานครบ ไม่ถูกย้อนกลับ
- **Error เดินทางขึ้น call stack (propagation)** — จาก function ชั้นใน → ชั้นนอก → global จนกว่าจะเจอ try-catch
- **ทุก function ใน call chain หยุดทำงาน** — ไม่ใช่แค่ function ที่ throw แต่ function ที่เรียกมันก็หยุดด้วย
- **ถ้าไม่มี try-catch** — โปรแกรมหยุดและแสดง error ใน console (try-catch คือบทถัดไป)
throw ใน function — การตรวจสอบและหยุดเมื่อเงื่อนไขไม่ผ่าน
หนึ่งใน patterns การใช้ `throw` ที่พบบ่อยที่สุดคือการ**ตรวจสอบ input ของ function แล้ว throw ถ้า input ไม่ถูกต้อง** pattern นี้เรียกว่า **guard clause** — เป็นการเช็คเงื่อนไขที่ไม่ต้องการตอนต้น function ถ้าเงื่อนไขนั้นเป็นจริง (input ไม่ถูกต้อง) ให้ throw error ทันที — function จะหยุดและไม่ดำเนินการส่วนที่เหลือ ประโยชน์ของ guard clause: - ทำให้โค้ดส่วนที่เหลือ (logic หลัก) ไม่ต้องกังวลเรื่อง input ที่ไม่ถูกต้อง — เพราะ input ที่ไม่ถูกต้องถูกกรองออกไปแล้ว - โค้ดอ่านง่าย — เห็นการตรวจสอบทั้งหมดอยู่ด้านบนของ function - error message ที่ชัดเจน — บอกได้ตรงจุดว่าอะไรไม่ถูกต้องและควรแก้อย่างไร
throw ใช้เช็คเงื่อนไขที่ไม่ต้องการตอนต้น function — ถ้า input ไม่ถูกต้อง function จะหยุดทันที
// ✅ ฟังก์ชันพร้อม guard clause — ตรวจสอบ input ก่อนทำงาน
function calculateDiscount(price, discountPercent) {
// Guard 1: ตรวจ type ของ argument
if (typeof price !== "number") {
throw new TypeError("price ต้องเป็นตัวเลขเท่านั้น");
}
if (typeof discountPercent !== "number") {
throw new TypeError("discountPercent ต้องเป็นตัวเลขเท่านั้น");
}
// Guard 2: ตรวจช่วงของค่า
if (price < 0) {
throw new RangeError("price ต้องมากกว่าหรือเท่ากับ 0");
}
if (discountPercent < 0 || discountPercent > 100) {
throw new RangeError("discountPercent ต้องอยู่ระหว่าง 0 ถึง 100");
}
// ✅ ผ่านทุก guard — input ถูกต้อง คำนวณต่อได้
const discount = price * (discountPercent / 100);
return price - discount;
}
// ✅ input ถูกต้อง — ทำงานได้ปกติ
console.log(calculateDiscount(1000, 20)); // 800
// ❌ input ผิด type — TypeError
// console.log(calculateDiscount("1000", 20));
// TypeError: price ต้องเป็นตัวเลขเท่านั้น
// ❌ input อยู่นอกช่วง — RangeError
// console.log(calculateDiscount(1000, 150));
// RangeError: discountPercent ต้องอยู่ระหว่าง 0 ถึง 100แยก guard แต่ละเงื่อนไขออกจากกัน — แต่ละ guard ตรวจเรื่องเดียว ทำให้ error message ชัดเจนและแก้ไขง่าย
// ✅ guard clause หลายชั้น — อ่านง่าย error message ชัดเจน
function registerUser(username, age, email) {
// Guard 1: ตรวจ type
if (typeof username !== "string") {
throw new TypeError("username ต้องเป็น string");
}
if (typeof age !== "number") {
throw new TypeError("age ต้องเป็นตัวเลข");
}
if (typeof email !== "string") {
throw new TypeError("email ต้องเป็น string");
}
// Guard 2: ตรวจค่าว่าง
if (username.trim() === "") {
throw new Error("username ห้ามเป็นค่าว่าง");
}
if (email.trim() === "") {
throw new Error("email ห้ามเป็นค่าว่าง");
}
// Guard 3: ตรวจช่วงของค่า
if (age < 0 || age > 150) {
throw new RangeError("age ต้องอยู่ระหว่าง 0 ถึง 150");
}
// Guard 4: ตรวจรูปแบบข้อมูล
if (!email.includes("@")) {
throw new Error("email ต้องมี @ — รูปแบบอีเมลไม่ถูกต้อง");
}
// ✅ ผ่านทุก guard — ข้อมูลถูกต้อง สร้างผู้ใช้ต่อได้
console.log("ลงทะเบียนผู้ใช้:", username);
return { username, age, email };
}
// ✅ ข้อมูลถูกต้อง
console.log(registerUser("สมชาย", 25, "somchai@email.com"));
// output:
// "ลงทะเบียนผู้ใช้: สมชาย"
// { username: "สมชาย", age: 25, email: "somchai@email.com" }
// ❌ ข้อมูลผิด — throw ที่ guard แรกที่เจอ
// console.log(registerUser("", 25, "test@email.com"));
// Error: username ห้ามเป็นค่าว่าง- **Guard clause** คือการตรวจสอบ input ตอนต้น function — ถ้าไม่ผ่านเงื่อนไขให้ throw ทันที
- **ข้อดี**: logic หลักไม่ต้องกังวลเรื่อง input ผิด, โค้ดอ่านง่าย, error message ชัดเจน
- **แยก guard ละเรื่อง**: แต่ละ guard ตรวจเงื่อนไขเดียว — ทำให้รู้ทันทีว่าปัญหาคืออะไร (ไม่ต้องไล่หลายเงื่อนไขใน if เดียว)
- **ลำดับ guard**: ตรวจ type ก่อน → ตรวจช่วงของค่า → ตรวจรูปแบบ — ไล่จากพื้นฐานไปเฉพาะเจาะจง
- **throw หยุด function ทันที**: ไม่ต้องเขียน `else` หรือ `return` — แค่ throw แล้ว function ก็จบ
การเลือก Error type ให้เหมาะกับสถานการณ์
เวลาเรา `throw` เราเลือกได้ว่าจะโยน Error ประเภทไหน — การเลือกให้เหมาะสมกับสถานการณ์ช่วยให้คนที่มาเจอ error (หรือตัวเราเองตอน debug) เข้าใจปัญหาได้เร็วขึ้น หลักการเลือก Error type: - ดูที่**สาเหตุของปัญหา** — ปัญหาเกิดจากอะไร? type ไม่ตรง? ค่าอยู่นอกช่วง? ข้อมูลไม่ถูกต้อง? - เลือก Error type ที่**สื่อสาเหตุนั้นได้ตรงที่สุด** — ให้ `.name` บอกประเภทปัญหา - ใส่ `.message` ที่**อธิบายว่าอะไรผิด** และ**ควรแก้อย่างไร** Error type ที่ใช้บ่อยตอน throw:
| Error type | ใช้เมื่อ | ตัวอย่างสถานการณ์ |
|---|---|---|
| `Error` | ข้อผิดพลาดทั่วไป — ไม่ตรงกับ type อื่นโดยเฉพาะ | ข้อมูลไม่ครบ, การดำเนินการล้มเหลว, เงื่อนไขทางธุรกิจไม่ผ่าน |
| `TypeError` | ค่าที่รับมามี type ไม่ตรงกับที่ function คาดหวัง | function รับ string แต่ได้รับ number, function รับ array แต่ได้รับ object |
| `RangeError` | ค่าอยู่นอกช่วงที่ยอมรับได้ — เช่นจำนวนลบเมื่อต้องการจำนวนบวก | อายุติดลบ, เปอร์เซ็นต์เกิน 100, จำนวนสินค้าน้อยกว่า 1 |
| `ReferenceError` | ไม่ค่อยใช้ throw เอง — มักถูก throw โดย JavaScript เมื่ออ้างอิงตัวแปรที่ไม่มีอยู่ | ไม่แนะนำให้ throw ReferenceError เอง — ใช้ Error หรือ TypeError แทน |
| `SyntaxError` | ไม่ควร throw เอง — SyntaxError เกิดตอน parse ก่อนโปรแกรมรัน | ไม่แนะนำให้ throw SyntaxError เอง — ใช้ Error หรือ TypeError แทน |
แต่ละสถานการณ์เหมาะกับ Error type ต่างกัน — เลือกให้ .name สื่อสาเหตุของปัญหาได้ตรงที่สุด
// ✅ TypeError — เมื่อ type ของค่าผิด
function repeat(text, times) {
if (typeof text !== "string") {
throw new TypeError("text ต้องเป็น string — แต่ได้รับ " + typeof text);
}
if (typeof times !== "number") {
throw new TypeError("times ต้องเป็นตัวเลข — แต่ได้รับ " + typeof times);
}
return text.repeat(times);
}
// ✅ RangeError — เมื่อค่าอยู่นอกช่วง
function setAge(age) {
if (age < 0 || age > 150) {
throw new RangeError("age ต้องอยู่ระหว่าง 0 ถึง 150 — แต่ได้รับ " + age);
}
console.log("อายุ:", age);
}
// ✅ Error ทั่วไป — เมื่อเป็นข้อผิดพลาดด้าน logic หรือเงื่อนไขทางธุรกิจ
function withdraw(balance, amount) {
if (amount <= 0) {
throw new Error("จำนวนเงินที่ถอนต้องมากกว่า 0 — แต่ได้รับ " + amount);
}
if (amount > balance) {
throw new Error("ยอดเงินคงเหลือไม่พอ — มี " + balance + " แต่ต้องการถอน " + amount);
}
return balance - amount;
}
// ตัวอย่างการใช้งาน
console.log(repeat("Hi", 3)); // "HiHiHi"
setAge(25); // "อายุ: 25"
console.log(withdraw(1000, 300)); // 700- **`Error`** — สำหรับข้อผิดพลาดทั่วไปที่ไม่ตรงกับ type อื่นโดยเฉพาะ (ใช้บ่อยที่สุดตอน throw เอง)
- **`TypeError`** — เมื่อค่าที่ได้รับมี type ไม่ตรงตามที่ function คาดหวัง (เช็คด้วย `typeof`)
- **`RangeError`** — เมื่อค่าอยู่นอกช่วงที่ยอมรับได้ (เช็คด้วย `>`, `<`, `>=`, `<=`)
- **`ReferenceError` และ `SyntaxError`** — ไม่แนะนำให้ throw เอง เพราะเป็น error ที่ JavaScript สร้างให้ในสถานการณ์เฉพาะ
- **ใส่ error message ที่เป็นประโยชน์**: บอกว่าอะไรผิด + ค่าที่ได้รับคืออะไร + ควรแก้อย่างไร
ข้อควรระวัง
`throw` เป็นเครื่องมือที่ทรงพลัง — แต่ก็มีจุดที่มือใหม่มักผิดพลาด มาดูข้อควรระวังที่สำคัญ:
การโยน string ทำให้เสียข้อมูลสำคัญ — ไม่มี .name, .message, และ stack trace ทำให้ debug ยาก
// ❌ โยน string — ไม่มีข้อมูล error ที่เป็นประโยชน์
function checkAge(age) {
if (age < 0) {
throw "อายุต้องมากกว่า 0"; // ❌ string — ไม่มี .name, ไม่มี stack trace
}
}
// checkAge(-5);
// ^ console แสดงแค่ "อายุต้องมากกว่า 0" — ไม่รู้ว่า error ประเภทอะไร เกิดที่บรรทัดไหน
// ✅ โยน Error object — มีข้อมูลครบ
function checkAge2(age) {
if (age < 0) {
throw new RangeError("age ต้องมากกว่าหรือเท่ากับ 0 — แต่ได้รับ " + age);
}
}
// checkAge2(-5);
// RangeError: age ต้องมากกว่าหรือเท่ากับ 0 — แต่ได้รับ -5
// at checkAge2 (...)
// ^ มี .name = "RangeError", .message = "age ต้องมากกว่า...", และ stack traceโค้ดหลัง throw ใน scope เดียวกันจะไม่ทำงาน — ต้องแน่ใจว่าสิ่งที่จำเป็นต้องทำได้ทำเสร็จก่อน throw
// ❌ throw ก่อนทำงานที่จำเป็น — ข้อมูลไม่ถูกบันทึก
function saveAndValidate(data) {
throw new Error("ข้อมูลไม่ถูกต้อง"); // ❌ throw ก่อน
console.log("บันทึกข้อมูลลง log"); // ❌ ไม่ทำงาน — log ไม่ถูกบันทึก
}
// ✅ ทำงานที่จำเป็นก่อน throw
function saveAndValidate2(data) {
console.log("บันทึกข้อมูลลง log — ได้รับ data:", data); // ✅ ทำงานก่อน throw
if (!data.name) {
throw new Error("ข้อมูลต้องมี name"); // ✅ throw หลังทำงานที่จำเป็น
}
return "ผ่าน";
}
console.log(saveAndValidate2({}));
// output:
// "บันทึกข้อมูลลง log — ได้รับ data: {}"
// Error: ข้อมูลต้องมี name
// ^ log ถูกบันทึกก่อน throw — ข้อมูลไม่หายthrow โดยไม่มี try-catch ทำให้โปรแกรมหยุดทำงาน — ในโค้ดจริง (โดยเฉพาะบนเว็บ) อาจทำให้ผู้ใช้เห็นหน้าจอว่าง
// ❌ throw ใน production โดยไม่มี try-catch — ผู้ใช้เห็น error
function getUserData(userId) {
if (!userId) {
throw new Error("userId is required"); // ❌ โปรแกรมหยุด — ผู้ใช้เห็น error
}
return { id: userId, name: "สมชาย" };
}
// ✅ แสดงข้อความที่เป็นมิตรแทน throw — หรือใช้ try-catch (บทถัดไป)
function getUserData2(userId) {
if (!userId) {
console.error("ข้อผิดพลาด: userId is required");
return null; // ✅ คืน null แทนการ throw — ผู้ใช้ไม่เห็น error dump
}
return { id: userId, name: "สมชาย" };
}
console.log(getUserData2("")); // null — จัดการได้
console.log(getUserData2(123)); // { id: 123, name: "สมชาย" }
// 💡 throw มีไว้สำหรับข้อผิดพลาดที่ "ควรหยุดการทำงาน" — ถ้าแค่ต้องการส่งสัญญาณ
// ว่าข้อมูลไม่ถูกต้องและให้ caller จัดการ return null หรือ return { error: "..." } ก็พอ
// บทถัดไป (try-catch) จะสอนวิธีดักจับ error ที่ถูก throw ขึ้นมา- **โยน string/number แทน Error object** — ทำให้เสีย `.name`, `.message`, และ stack trace → debug ยาก ควรโยน `new Error()` หรือ subtype เสมอ
- **ลืมว่า throw หยุดการทำงานทันที** — โค้ดที่จำเป็นต้องทำ (บันทึก log, ปิดการเชื่อมต่อ, คืนค่าบางอย่าง) ต้องทำก่อน throw — หรือใช้ `try-catch-finally` (บทถัดไป)
- **ใช้ throw โดยไม่มี try-catch รองรับ** — ใน production ทำให้โปรแกรมหยุดและผู้ใช้เห็น error dump — ถ้าควรจัดการได้ให้ return ค่าพิเศษแทน (เช่น `null`), หรือใช้ try-catch
- **throw ใน callback หรือ async code** — throw ใน `setTimeout`, `fetch().then()`, หรือ `Promise` ไม่สามารถดักจับด้วย try-catch ภายนอกได้ — เป็นเรื่องที่สอนในบท Async JavaScript ภายหลัง
- **ใช้ throw มากเกินไปแทนการ return** — throw มีไว้สำหรับข้อผิดพลาดร้ายแรงที่ควรหยุดการทำงาน — ถ้าเป็นเรื่องที่ caller จัดการได้ตามปกติ (เช่นข้อมูลไม่ครบ), return ค่าพิเศษก็พอ
สรุป — throw
- **`throw`** คือ keyword ที่หยุดการทำงานทันทีและส่ง error ขึ้น call stack — ต่างจาก `new Error()` ที่เป็นแค่การสร้าง object
- **syntax**: `throw expression;` — ควรโยน `Error` object หรือ subtype (`TypeError`, `RangeError`) — ไม่โยน string หรือ number เปล่า
- **เมื่อ throw**: โค้ดหลัง throw ใน scope เดียวกันไม่ทำงาน — error เดินทางขึ้น call stack (propagation) จนกว่าจะเจอ try-catch หรือโปรแกรมหยุด
- **Guard clause**: pattern การตรวจสอบ input ตอนต้น function — ถ้าไม่ผ่านเงื่อนไขให้ throw ทันที ทำให้ logic หลักสะอาดและไม่ต้องกังวลเรื่อง input ผิด
- **เลือก Error type ให้เหมาะกับสถานการณ์**: `TypeError` สำหรับ type ผิด, `RangeError` สำหรับค่าอยู่นอกช่วง, `Error` สำหรับข้อผิดพลาดทั่วไป
- **ข้อควรระวัง**: โยน Error object ไม่ใช่ string, ทำงานที่จำเป็นก่อน throw, ไม่ใช้ throw โดยไม่มี try-catch รองรับใน production
| สิ่งที่อยากรู้ | คำตอบ |
|---|---|
| `new Error()` กับ `throw new Error()` ต่างกันอย่างไร | `new Error()` สร้าง object เฉย ๆ — โปรแกรมไม่หยุด `throw new Error()` สร้าง error และหยุดโปรแกรมทันที |
| ควร throw อะไร | `Error` object หรือ subtype (`TypeError`, `RangeError`, ฯลฯ) — ไม่โยน string หรือ number |
| throw แล้วเกิดอะไรขึ้น | โค้ดหลัง throw ไม่ทำงาน — error เดินทางขึ้น call stack จนกว่าจะเจอ try-catch (หรือโปรแกรมหยุด) |
| ควร throw ตอนไหน | เมื่อเจอข้อผิดพลาดที่ทำให้ดำเนินการต่อไม่ได้ — เช่น input ไม่ถูกต้อง, ข้อมูลไม่ครบ, สถานะที่ยอมรับไม่ได้ |
| throw ใน function ต่างจาก return อย่างไร | `return` จบ function ปกติและคืนค่ากลับ caller — `throw` จบ function แบบผิดพลาดและเดินทางขึ้น call stack จนกว่าจะมีคนดักจับ |
| ถ้าไม่มี try-catch จะเกิดอะไรขึ้น | โปรแกรมหยุดทำงานและแสดง error ใน console — try-catch คือบทถัดไปที่จะสอนวิธีจัดการ error โดยไม่ให้โปรแกรมหยุด |
`throw` เป็นกลไกพื้นฐานที่ JavaScript ใช้ส่งสัญญาณข้อผิดพลาด — เมื่อเราเข้าใจ `throw` แล้ว ขั้นต่อไปคือการเรียนรู้วิธี**ดักจับและจัดการ** error ที่ถูก throw ขึ้นมาด้วย `try-catch-finally` ซึ่งเป็นบทถัดไป