Skip to content

บทที่ 11 — Error Handling: จัดการข้อผิดพลาด

← บทที่ 10 | สารบัญ | บทที่ 12: DOM

โปรแกรมจริงต้องเจอ "ข้อผิดพลาด" เสมอ — server ล่ม, ผู้ใช้กรอกข้อมูลผิด, ไฟล์หาย. บทนี้สอนวิธีจัดการให้โปรแกรมไม่พังทั้งระบบ

อ่านจบบทนี้คุณจะ:

  • เข้าใจ Error object และวิธีสร้าง error ของตัวเอง
  • ใช้ try/catch/finally ได้ถูกต้อง
  • จัดการ error ในโค้ด async
  • รู้ว่าควร "throw" หรือ "return error" เมื่อไหร่

ใช้เวลา 2 ชั่วโมง


Part 1: Error คืออะไร

1.1 2 ประเภทของปัญหา

ในโปรแกรมมีปัญหา 2 แบบ:

1. Bug (ข้อผิดพลาดของ programmer) — โค้ดเขียนผิด เช่นเรียก property ของ undefined, พิมพ์ชื่อตัวแปรผิด → ต้องแก้โค้ด

2. Error ที่คาดได้ (สถานการณ์ที่อาจเกิด) — server ล่ม, ผู้ใช้กรอกผิด, ไฟล์ไม่มี → ต้อง "จัดการ" ให้โปรแกรมรับมือได้

บทนี้เน้นเรื่องที่ 2 — การ "จัดการ" error ที่อาจเกิด

1.2 ถ้าไม่จัดการ error จะเกิดอะไร

ตัวอย่างด้านล่างใช้ JSON.parse() ซึ่งเป็นฟังก์ชันในตัวสำหรับแปลง string ให้เป็น object (จะเรียนละเอียดเรื่อง JSON ในบทต่อ ๆ ไป) — ถ้าข้อมูลที่ส่งเข้ามาไม่ใช่ JSON ที่ถูกต้อง มันจะ throw error

javascript
const data = JSON.parse("ข้อความที่ไม่ใช่ JSON");
console.log("บรรทัดนี้ไม่ทำงาน");

JSON.parse เจอข้อความผิดรูปแบบ → มัน "throw error" → โปรแกรมหยุดทันที บรรทัดถัดไปไม่ทำงาน

ใน console จะเห็นข้อความประมาณนี้ (ข้อความจริงต่างเล็กน้อยตาม browser/Node):

text
SyntaxError: Unexpected token 'ข', "ข้อความที่ไม่ใช่ JSON" is not valid JSON
    at JSON.parse (<anonymous>)
    at ...

แปลเป็นไทย: "ผิด syntax — เจอตัวอักษร ที่ไม่คาดคิด, ข้อความ ข้อความที่ไม่ใช่ JSON ไม่ใช่ JSON ที่ถูกต้อง" — บรรทัดที่ขึ้นต้นด้วย at คือ "ร่องรอย" (stack trace) ที่บอกว่า error เกิดจากการเรียก JSON.parse

→ ถ้าเป็นเว็บ — ทั้งหน้าอาจค้าง. เราต้อง "จับ" error นี้ไว้


Part 2: Error Object

2.1 Error คือ object

ใน JavaScript error ไม่ใช่แค่ข้อความ แต่เป็น object ที่มี property สำคัญ — message (ข้อความ), name (ชนิด), และ stack (บอกว่า error เกิดที่บรรทัดไหน) เข้าใจโครงสร้างนี้ก่อนจะช่วยให้จัดการ error ได้ดี:

javascript
const error = new Error("เกิดข้อผิดพลาด");

error.message        // "เกิดข้อผิดพลาด" — ข้อความ
error.name           // "Error" — ชนิด
error.stack          // ข้อความบอกว่า error เกิดที่บรรทัดไหน (call stack)

2.2 ชนิดของ Error ที่มีมาให้

JavaScript มี Error หลายชนิดในตัว:

javascript
TypeError        // ใช้ค่าผิดชนิด — เช่น เรียก method ของ undefined
RangeError       // ค่าเกินขอบเขต — เช่น สร้าง array ขนาดติดลบ
ReferenceError   // ใช้ตัวแปรที่ไม่มี — เช่น พิมพ์ชื่อตัวแปรผิด
SyntaxError      // syntax ผิด — เช่น JSON ผิดรูปแบบ, วงเล็บไม่ครบ

แปลความหมายคร่าว ๆ:

  • TypeError = "ผิดชนิดข้อมูล" (Type = ชนิด)
  • RangeError = "เกินช่วงที่อนุญาต" (Range = ช่วง/ขอบเขต)
  • ReferenceError = "อ้างถึงสิ่งที่ไม่มี" (Reference = การอ้างถึง)
  • SyntaxError = "ไวยากรณ์ผิด" (Syntax = ไวยากรณ์)

แต่ละชนิดบอกประเภทของปัญหา — มีประโยชน์ตอนแยกแยะว่าเกิดอะไรขึ้น

2.3 throw — โยน error

throw = "ส่งสัญญาณว่าเกิดข้อผิดพลาด" — มันหยุดการทำงานปัจจุบันทันที:

javascript
function divide(a, b) {
  if (b === 0) {
    throw new Error("หารด้วยศูนย์ไม่ได้");
  }
  return a / b;
}

⚠️ throw แต่ Error object เสมอ — อย่า throw primitive (string, number, boolean, null, undefined):

javascript
throw "error";              // ❌ ไม่ดี — ไม่มี stack trace
throw 42;                   // ❌ ไม่ดี — ไม่มีข้อมูลเพิ่ม
throw new Error("error");   // ✅ ดี — มีข้อมูลครบ

💡 new Error("...") = สร้าง Error object ใหม่ (คล้ายกับ new Date() ที่เรียนในบทที่ 08) — ตัว object นี้มี .message, .name, .stack ที่ช่วย debug ได้มาก

ทำไม? — Error object มี .stack ที่บอกว่า error เกิดที่ไหน ช่วย debug มาก. string ไม่มี

💡 stack trace คือรายการแสดงว่า error เกิดที่บรรทัดไหน และถูกเรียกผ่านฟังก์ชันไหนมาบ้าง — เห็นได้ใน console เวลาโปรแกรมพัง (บรรทัดที่ขึ้นต้นด้วย at ในตัวอย่าง Part 1.2)


Part 3: try / catch / finally

try/catch คือกลไกหลักในการ "จับ" error

3.1 พื้นฐาน

javascript
try {
  const data = JSON.parse("ข้อความผิดรูปแบบ");
  console.log(data);
} catch (error) {
  console.log("จับ error ได้:", error.message);
}

console.log("โปรแกรมทำงานต่อได้");      // ✅ บรรทัดนี้ทำงาน

อธิบาย:

  • try { ... } — "ลองทำโค้ดนี้"
  • catch (error) { ... } — "ถ้าเกิด error ในส่วน try → กระโดดมาทำตรงนี้ พร้อม error object"

ผลคือ — แม้ JSON.parse พัง โปรแกรมไม่หยุด — มันกระโดดมา catch แล้วทำต่อ

3.2 finally — ทำเสมอ

javascript
try {
  doSomething();
} catch (error) {
  handleError(error);
} finally {
  cleanup();          // ทำเสมอ — ไม่ว่า try สำเร็จหรือ catch ทำงาน
}

finally ทำงานเสมอ — ไม่ว่าจะมี error หรือไม่ — ใช้ทำ "งานเก็บกวาด" เช่นปิดไฟล์, ปิด connection

3.3 catch แบบไม่ใช้ error

ตั้งแต่ ES2019 ถ้าใน catch ไม่ได้ใช้ error object เลย เราละ parameter ทิ้งได้ (เขียน catch { เฉย ๆ) — โค้ดสะอาดขึ้นเมื่อแค่อยากรู้ว่า "พังหรือไม่":

javascript
try {
  doSomething();
} catch {
  console.log("มีบางอย่างผิดพลาด");
}

3.4 try/catch จับอะไรได้บ้าง

try/catch จับได้:

  • error จาก throw
  • error ที่ JavaScript โยนเอง (เช่น TypeError)

try/catch จับไม่ได้:

  • error ใน callback ที่ async (เช่นใน setTimeout)
javascript
try {
  setTimeout(() => {
    throw new Error("พัง");      // ❌ try/catch จับไม่ได้!
  }, 1000);
} catch (error) {
  // ไม่มาถึงตรงนี้
}

ทำไม? — เพราะ callback ของ setTimeout ทำงาน "ทีหลัง" (ตอนนั้น try/catch จบไปแล้ว) → เรื่องนี้ Part 5 อธิบายการจัดการ async error

⚠️ และเรื่องร้ายแรงกว่านั้น: ถ้า throw error ใน callback ของ setTimeout แล้วไม่มีใครจับ — ใน Node.js จะเป็น uncaughtException ทำให้ process crash หยุดทั้งโปรแกรม (default behavior). ในเบราว์เซอร์จะเห็น error ขึ้นใน console และ tab ทำงานต่อได้ แต่ใน Node ห้ามปล่อยทิ้งเด็ดขาด — ต้องห่อ try/catch ใน callback เอง หรือใช้ Promise


Part 4: สร้าง Custom Error

ในงานจริง — เราสร้าง Error ชนิดของตัวเองเพื่อแยกแยะปัญหา

4.1 สร้าง class ที่ extends Error

เราสร้าง error ชนิดของตัวเองได้โดย extends Error — เพื่อแยกประเภท error (เช่น ValidationError, NotFoundError) และแนบข้อมูลเพิ่ม (เช่น field ที่ผิด) ทำให้ catch แล้วจัดการต่างกันตามชนิดได้:

💡 ทบทวนสั้น ๆ (จากบทที่ 06): extends Error = "สร้าง error ชนิดใหม่ที่สืบทอดความสามารถจาก Error เดิม" — super(message) = บอกให้ Error ต้นแบบเก็บ message ให้ ถ้าลืมเรื่อง class/extends ลองกลับไปดูบทที่ 06 ก่อนได้

javascript
class ValidationError extends Error {
  constructor(message, field) {
    super(message);                  // เรียก constructor ของ Error
    this.name = "ValidationError";    // ตั้งชื่อชนิด
    this.field = field;               // ใส่ข้อมูลเพิ่ม
  }
}

class NotFoundError extends Error {
  constructor(resource) {
    super(`ไม่พบ ${resource}`);
    this.name = "NotFoundError";
  }
}

4.2 ใช้ custom error

javascript
function createUser(data) {
  if (!data.email) {
    throw new ValidationError("ต้องกรอก email", "email");
  }
  if (!data.email.includes("@")) {
    throw new ValidationError("email ไม่ถูกต้อง", "email");
  }
  // ...
}

try {
  createUser({ name: "Alice" });
} catch (error) {
  if (error instanceof ValidationError) {
    console.log(`ฟิลด์ ${error.field}: ${error.message}`);
  } else {
    console.log("error อื่น:", error.message);
  }
}

instanceof ใช้เช็คว่า error เป็นชนิดไหน → จัดการต่างกันได้

4.3 Error Cause — ห่อ error เดิม

ES2022 เพิ่ม cause — ใช้ "ห่อ" error เดิมไว้ในข้อความใหม่:

📌 เวอร์ชันที่รองรับ: ฟีเจอร์นี้ใช้ได้ใน Node.js 16.9+, Chrome 93+, Firefox 91+, Safari 15+ — ปี 2026 ถือว่าใช้ได้ทุกที่แล้ว แต่ถ้าต้องรองรับ Node เก่ากว่านั้น (เช่น project legacy) อาจต้องเก็บ error เดิมไว้ใน property อื่นเอง

javascript
const url = "https://api.example.com/users/1";   // url ถูกกำหนดมาก่อน

try {
  await fetch(url);
} catch (originalError) {
  throw new Error("โหลดข้อมูลผู้ใช้ล้มเหลว", { cause: originalError });
}

ปลายทาง:

javascript
catch (error) {
  console.log(error.message);    // "โหลดข้อมูลผู้ใช้ล้มเหลว"
  console.log(error.cause);       // error เดิมจาก fetch
}

→ ใช้บอก "ระดับสูง" ว่าเกิดอะไร พร้อมเก็บ "ระดับล่าง" (สาเหตุจริง) ไว้

📌 error.cause อาจเป็นค่าอะไรก็ได้ (ไม่จำเป็นต้องเป็น Error object) — เวลาใช้งานควรเช็คก่อน: error.cause instanceof Error หรือใช้ optional chaining error.cause?.message เพื่อกันกรณีที่ cause เป็น string หรือ null


Part 5: จัดการ Error ใน Async

จากบทที่ 09 — โค้ด async มีวิธีจัดการ error ของตัวเอง

5.1 ใน Promise — ใช้ .catch

ในโค้ดแบบ Promise chain (.then) เราจับ error ด้วย .catch ต่อท้าย — มันดักทุก error ที่เกิดใน .then ก่อนหน้าทั้งหมดไว้ที่จุดเดียว:

javascript
fetch(url)
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.log("ผิดพลาด:", error.message));

.catch จับ error จากทุก .then ก่อนหน้า

5.2 ใน async/await — ใช้ try/catch

javascript
async function loadUser() {
  try {
    const response = await fetch(url);
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`);
    }
    const data = await response.json();
    return data;
  } catch (error) {
    console.log("โหลดล้มเหลว:", error.message);
    return null;
  }
}

ใน async/await — try/catch จับ error ของ Promise ที่ await ได้ (ต่างจากกรณี setTimeout ใน Part 3.4 — เพราะ await "รอ" ตรงนั้นจริง ๆ)

5.3 ⚠️ Unhandled Rejection — ลืมจับ error ของ Promise

javascript
async function risky() {
  throw new Error("พัง");
}

risky();      // ❌ ไม่มีใครจับ error → "Unhandled Promise Rejection"

ถ้าเรียก async function แล้วไม่จับ error — error จะ "หลุด" → ใน Node.js โปรแกรมอาจ crash

เรียก async function ต้องจับ error เสมอ:

javascript
risky().catch(error => console.log(error));
// หรือ
try { await risky(); } catch (error) { ... }

5.4 ดักจับ error ที่หลุดทั้งหมด (safety net)

วางตาข่ายนิรภัยกัน error ที่หลุดจริง ๆ — โค้ดด้านล่างใช้ API พิเศษคนละตัวกัน: window มีเฉพาะในเบราว์เซอร์, process มีเฉพาะใน Node.js — ต้องเลือกใช้ตาม environment

💡 global คืออะไรwindow และ process เป็น "global object" คือตัวแปรที่เรียกใช้ได้ทันทีจากทุกที่ในโปรแกรม โดยไม่ต้อง import หรือประกาศเอง — เบราว์เซอร์เตรียม window ไว้ให้, Node.js เตรียม process ไว้ให้

💡 เลือกใช้อันไหน? — ถ้ารันโค้ดใน Node.js (เช่น ทำแบบฝึกหัดบนเครื่องส่วนตัว) ใช้ process.on — ถ้าเขียนโค้ดสำหรับเบราว์เซอร์ ใช้ window.addEventListener — โดยปกติตอนเรียนเราใช้ Node ก็ให้ใช้ process.on

javascript
// ในเบราว์เซอร์ — window มีให้อัตโนมัติ
window.addEventListener("unhandledrejection", (event) => {
  console.log("Promise error ที่ไม่ถูกจับ:", event.reason);
});

// ใน Node.js — process เป็น global ที่ Node ให้มา
process.on("unhandledRejection", (reason) => {
  console.log("Promise error ที่ไม่ถูกจับ:", reason);
});

⚠️ Node 15+ — unhandled rejection ทำ process crash โดย default: ตั้งแต่ Node.js เวอร์ชัน 15 เป็นต้นมา ถ้ามี Promise rejection ที่ไม่ถูกจับ → process จะ exit ทันที (ก่อนหน้านี้แค่ขึ้น warning) — โค้ด legacy ที่ยังพึ่งพฤติกรรมเดิม (แค่ warning ไม่ crash) สามารถสั่งรันด้วย flag --unhandled-rejections=warn ได้ แต่นั่นเป็นทางแก้ชั่วคราวเท่านั้น — แนวทาง production ที่ถูกต้องคือจับ error ให้ครบตั้งแต่ต้น ไม่ใช่ใช้ flag เพื่อปิด safety check ถ้าใช้ process.on('unhandledRejection', ...) ควร log error แล้วเรียก process.exit(1) เพื่อให้ process manager (เช่น PM2 — โปรแกรมช่วยดูแล/รีสตาร์ท Node.js process อัตโนมัติ เป็นเครื่องมือแยกต่างหาก ไม่ได้มากับ Node) รีสตาร์ทโปรแกรมให้ — ไม่ใช่กลืน error เงียบ ๆ

→ อันนี้เป็น "ตาข่ายสุดท้าย" — ไม่ใช่วิธีจัดการหลัก. ควรจับ error ตรงจุดที่เกิด


Part 6: throw หรือ return error — เลือกยังไง

มี 2 วิธีบอกว่า "เกิดข้อผิดพลาด" — และต้องเลือกให้ถูก

6.1 วิธี throw

วิธีแรกคือ throw — โยน error ออกไปให้ผู้เรียกจัดการด้วย try/catch เหมาะกับ error ที่ "ไม่ควรเกิด" และอยากหยุดการทำงานทันที:

javascript
function getUser(id) {
  if (!id) throw new Error("ต้องระบุ id");
  // ...
}

6.2 วิธี return error (result pattern)

อีกวิธีคือ "คืน" ผลลัพธ์ที่บอกสถานะ (เช่น { ok: false, error: ... }) แทนการ throw — เหมาะกับ error ที่ "คาดว่าจะเกิดได้" อย่าง validation ทำให้ผู้เรียกต้องเช็คผลก่อนใช้ ชัดเจนกว่า:

javascript
function validateEmail(email) {
  if (!email) return { ok: false, error: "ต้องกรอก email" };
  if (!email.includes("@")) return { ok: false, error: "email ไม่ถูกต้อง" };
  return { ok: true };
}

const result = validateEmail("test");
if (!result.ok) {
  console.log(result.error);
}

6.3 เลือกอันไหน

ใช้ throw เมื่อ — เป็นข้อผิดพลาดที่ "ไม่คาดคิด / ผิดปกติ":

  • database เชื่อมไม่ได้
  • argument ที่ส่งมาผิดอย่างร้ายแรง
  • สถานการณ์ที่ "ไม่ควรเกิด"

ใช้ return error เมื่อ — เป็นกรณีที่ "คาดได้ เป็นเรื่องปกติ":

  • form validation (ผู้ใช้กรอกผิดเป็นเรื่องปกติ)
  • "ไม่พบข้อมูล" (อาจไม่มีจริง ๆ ก็ได้)
  • login ผิด

หลักการ: ถ้ามันเป็น "เรื่องปกติที่เกิดได้บ่อย" → return error (เพราะ throw ทุกครั้งทำให้โค้ดวุ่นวาย). ถ้าเป็น "เรื่องผิดปกติร้ายแรง" → throw


Part 7: หลักการเขียน Error Handling ที่ดี

7.1 อย่ากลืน error เงียบ ๆ

javascript
// ❌ แย่มาก — error หายไปไม่มีใครรู้
try {
  doSomething();
} catch (error) {
  // ว่างเปล่า — กลืน error เงียบ ๆ
}

ถ้าจับ error แล้วไม่ทำอะไรเลย — บั๊กจะซ่อนอยู่ หาไม่เจอ. อย่างน้อยต้อง log:

javascript
try {
  doSomething();
} catch (error) {
  console.error("เกิดข้อผิดพลาด:", error);
}

7.2 จับ error ที่เฉพาะเจาะจง

อย่าจับ error แบบเหมารวมแล้วทำเหมือนกันหมด — ควรเช็คชนิด error (ด้วย instanceof) แล้วจัดการต่างกันตามประเภท ส่วน error ที่ไม่รู้จักควรโยนต่อ ไม่กลืนเงียบ:

javascript
// ❌ จับทุกอย่างแล้วทำเหมือนกันหมด
catch (error) {
  console.log("เกิดข้อผิดพลาด");
}

// ✅ แยกแยะตามชนิด
catch (error) {
  if (error instanceof ValidationError) {
    showFormError(error.field);
  } else if (error instanceof NotFoundError) {
    show404Page();
  } else {
    throw error;          // error ที่ไม่รู้จัก — โยนต่อ ให้คนข้างบนจัดการ
  }
}

7.3 ตรวจพบปัญหาแล้วหยุดเลย (fail fast) — เช็คก่อน ผิดก็หยุดทันที

fail fast = หลักการ "เจอปัญหาให้หยุดทันที ไม่ปล่อยให้ทำงานต่อ" — ดีกว่าปล่อยให้โปรแกรมทำงานต่อไปบนข้อมูลที่ผิด แล้วไปพังลึก ๆ ที่หาสาเหตุยาก (คำนี้แปลตรงตัวว่า "พังเร็ว" ซึ่งฟังดูไม่ดี แต่ในวงการ programming ถือเป็นแนวทางที่ดี เพราะพังให้เห็นทันทีดีกว่าพังแบบซ่อนอยู่)

javascript
function processOrder(order) {
  if (!order) throw new Error("ไม่มี order");
  if (!order.items) throw new Error("order ไม่มีรายการสินค้า");
  if (order.items.length === 0) throw new Error("order ว่างเปล่า");

  // ถึงตรงนี้ = order ถูกต้องแน่นอน
  // ทำงานหลัก...
}

เช็คเงื่อนไขผิด ๆ ก่อน แล้ว throw ทันที — ดีกว่าปล่อยให้ error ไปโผล่ลึก ๆ (จำ early return จากบทที่ 04)

7.4 อย่าใช้ exception (ข้อผิดพลาดที่โปรแกรมโยนออกมา) ควบคุมการทำงานปกติ

javascript
// ❌ ใช้ try/catch แทน if
try {
  const user = users.find(u => u.id === id);
  return user.name;
} catch {
  return "ไม่พบ";
}

// ✅ ใช้ if เช็คตามปกติ
const user = users.find(u => u.id === id);
return user ? user.name : "ไม่พบ";

try/catch มีไว้จับ "ข้อผิดพลาด" — ไม่ใช่แทน if ในการตัดสินใจปกติ


Part 8: Logging — บันทึกสิ่งที่เกิดขึ้น

console มี method หลายตัวสำหรับ log:

javascript
console.log("ข้อมูลทั่วไป");
console.info("ข้อมูล");
console.warn("คำเตือน");           // สีเหลือง
console.error("ข้อผิดพลาด");        // สีแดง
console.table([{a:1}, {a:2}]);     // แสดงเป็นตาราง
console.time("งาน"); /* ... */ console.timeEnd("งาน");   // จับเวลา

ในงาน production จริง — เราใช้ logging library (เช่น pino, winston สำหรับ Node.js) ที่บันทึก log แบบมีโครงสร้าง ส่งไปเก็บที่ส่วนกลาง — แต่ตอนเรียน console ก็พอ

8.1 รายงาน error ใน production

ในเว็บจริง — เราใช้ service ที่รวบรวม error จากผู้ใช้ทุกคน สำหรับมือใหม่แนะนำ Sentry เพราะมี free tier และเอกสารดี (มีคู่แข่งเช่น Bugsnag, Rollbar ด้วย — แต่เริ่มด้วย Sentry ก็พอ):

Sentry = บริการคลาวด์ที่รับ error report จากแอปเรา รวมข้อมูล stack trace + เวอร์ชัน + browser + user — แล้วแสดงเป็น dashboard ให้ทีมเห็นว่า bug ไหนเจอบ่อย

javascript
// ตัวอย่างแนวคิด (ต้อง setup Sentry ก่อน)
try {
  doSomething();
} catch (error) {
  Sentry.captureException(error);    // ส่ง error ไปเก็บที่ Sentry
}

→ ทำให้รู้ว่าผู้ใช้จริงเจอ error อะไรบ้าง — ตอนนี้แค่รู้จักว่ามี


Part 9: Lab — ลงมือทำ

Lab 1: try/catch พื้นฐาน

ฝึกห่อโค้ดที่อาจพังด้วย try/catch — ตัวอย่างคลาสสิกคือ JSON.parse ที่โยน error เมื่อ input ไม่ใช่ JSON ที่ถูกต้อง แล้วคืน null แทนการปล่อยให้โปรแกรมล่ม:

javascript
function parseJSON(text) {
  try {
    return JSON.parse(text);
  } catch (error) {
    console.log("JSON ไม่ถูกต้อง:", error.message);
    return null;
  }
}

console.log(parseJSON('{"name":"Alice"}'));   // { name: "Alice" }
console.log(parseJSON('ไม่ใช่ JSON'));         // null

Lab 2: Custom Error

ฝึกสร้าง custom error ที่แนบข้อมูลเพิ่ม (ยอดเงิน, จำนวนที่ขาด) แล้ว catch โดยเช็ค instanceof เพื่อจัดการเฉพาะกรณีนั้น — รวมทุกแนวคิดของบท:

javascript
class InsufficientFundsError extends Error {
  constructor(balance, amount) {
    super(`ยอดเงิน ${balance} ไม่พอจ่าย ${amount}`);
    this.name = "InsufficientFundsError";
    this.balance = balance;
    this.amount = amount;
  }
}

function withdraw(balance, amount) {
  if (amount > balance) {
    throw new InsufficientFundsError(balance, amount);
  }
  return balance - amount;
}

try {
  withdraw(100, 500);
} catch (error) {
  if (error instanceof InsufficientFundsError) {
    console.log(`ขาดอีก ${error.amount - error.balance} บาท`);
  }
}

Lab 3: async error handling

ฝึกจัดการ error ในโค้ด async จริง — ใช้ try/catch ครอบ await fetch พร้อมเช็ค response.ok เพื่อแยกกรณี network error กับ HTTP error:

javascript
async function fetchUser(id) {
  try {
    const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`);
    }
    return await response.json();
  } catch (error) {
    console.log("โหลด user ล้มเหลว:", error.message);
    return null;
  }
}

// บรรทัดด้านล่างใช้ top-level await — ต้องรันในไฟล์ ES module
// (ตั้ง "type": "module" ใน package.json หรือใช้นามสกุล .mjs)
// ถ้าใช้ CommonJS ให้ห่อใน async function: (async () => { ... })()
const user = await fetchUser(1);
console.log(user);

Lab 4: Result pattern

javascript
function safeDivide(a, b) {
  if (typeof a !== "number" || typeof b !== "number") {
    return { ok: false, error: "ต้องเป็นตัวเลข" };
  }
  if (b === 0) {
    return { ok: false, error: "หารด้วยศูนย์ไม่ได้" };
  }
  return { ok: true, value: a / b };
}

const result = safeDivide(10, 2);
if (result.ok) {
  console.log("ผลลัพธ์:", result.value);
} else {
  console.log("ผิดพลาด:", result.error);
}

Part 10: Checkpoint

  1. ถ้าไม่จับ error ที่ throw เกิดอะไรขึ้น?
  2. Error object มี property อะไรบ้าง?
  3. ทำไมต้อง throw new Error(...) ไม่ใช่ throw "string"?
  4. try/catch/finallyfinally ทำงานเมื่อไหร่?
  5. try/catch จับ error ใน setTimeout callback ได้ไหม? ทำไม?
  6. สร้าง custom error ทำยังไง?
  7. จัดการ error ใน async/await ใช้อะไร? ใน Promise chain ใช้อะไร?
  8. "Unhandled Promise Rejection" คืออะไร?
  9. throw กับ return error — เลือกอันไหนเมื่อไหร่?
  10. ทำไมไม่ควร "กลืน error เงียบ ๆ"?

Part 11: สรุปบทนี้

  • Error เป็น object — มี .message, .name, .stack
  • throw new Error(...) เสมอ — อย่า throw string
  • try/catch/finally — catch จับ error, finally ทำเสมอ
  • try/catch จับ error ใน async callback (setTimeout) ไม่ได้ — แต่จับ Promise ที่ await ได้
  • Custom Error (extends Error) — แยกแยะชนิดของปัญหา
  • async error: ใช้ try/catch กับ async/await, ใช้ .catch กับ Promise chain
  • เรียก async function ต้องจับ error เสมอ (ไม่งั้น unhandled rejection)
  • throw สำหรับเรื่องผิดปกติร้ายแรง / return error สำหรับเรื่องปกติ (validation)
  • อย่ากลืน error เงียบ — อย่างน้อยต้อง log

บทต่อไป — DOM: ควบคุมหน้าเว็บ


← บทที่ 10 | สารบัญ | บทที่ 12: DOM