.catch(callback) ดัก error จาก reject และ throw
error มาจาก reject() ใน executor หรือ throw ใน .then() ก็ตาม — .catch() ดักได้ทั้งหมด
Promises
ดัก error ด้วย .catch(), recovery ด้วย fallback value, cleanup ด้วย .finally() และทำความเข้าใจ error propagation ใน Promise chain
ในหน้าที่แล้ว (Creating Promises) คุณเห็นแล้วว่า `reject(error)` ส่ง error ไปให้ `.catch()` — แต่ยังไม่ได้ลงลึกว่า `.catch()` ทำอะไรได้บ้าง หน้านี้จะสอน: - **`.catch()`** ดัก error ทั้งจาก `reject()` และ `throw` ใน chain - **Error propagation** — error ไหลผ่าน chain อัตโนมัติจนกว่าจะเจอ `.catch()` - **Recovery** — คืนค่า fallback จาก `.catch()` เพื่อให้ chain ทำงานต่อ - **`.finally()`** — cleanup ที่ทำงานเสมอไม่ว่าจะสำเร็จหรือล้มเหลว
reject(new Error(...)) → error ไหลไป .catch() → callback ได้ error object
var promise = new Promise(function (resolve, reject) {
reject(new Error("ไม่พบข้อมูล"));
});
promise
.then(function (value) {
console.log("สำเร็จ:", value); // ไม่ทำงาน — Promise rejected
})
.catch(function (err) {
console.log("ดัก error:", err.message); // ดัก error: ไม่พบข้อมูล
});
// output:
// ดัก error: ไม่พบข้อมูลสังเกต: - `.then()` callback **ไม่ทำงาน** เพราะ Promise เป็น `rejected` ไม่ใช่ `fulfilled` - `.catch()` callback **ทำงาน** และได้รับ error object เป็น parameter — เข้าถึง `err.message` ได้ `.catch(callback)` เป็นวิธีเขียนแบบสั้นของ `.then(null, callback)` — ทำงานเหมือนกันทุกอย่าง:
เขียน .catch() หรือ .then(null, handler) ก็ดัก error ได้เหมือนกัน — แต่ .catch() อ่านง่ายกว่า
// .catch() — วิธีที่นิยมใช้
Promise.reject(new Error("ล้มเหลว"))
.catch(function (err) {
console.log("ดักด้วย .catch():", err.message);
});
// .then(null, handler) — ผลลัพธ์เหมือนกัน
Promise.reject(new Error("ล้มเหลว"))
.then(null, function (err) {
console.log("ดักด้วย .then(null, handler):", err.message);
});
// output:
// ดักด้วย .catch(): ล้มเหลว
// ดักด้วย .then(null, handler): ล้มเหลวใช้ .catch() ไม่ใช่ .then(null, handler)
แม้จะทำงานเหมือนกัน แต่ .catch() บอกจุดประสงค์ได้ชัดกว่า — ใครอ่านโค้ดก็รู้ทันทีว่าบรรทัดนี้ดัก error
`.catch()` ไม่ได้ดักแค่ `reject()` — มันดัก `throw` ใน `.then()` ได้ด้วย: ถ้าโค้ดใน `.then()` callback `throw` error → Promise ตัวใหม่ที่ `.then()` คืนออกมาจะเป็น `rejected` → `.catch()` ถัดไปจะดักได้
throw ใน .then() สร้าง rejected Promise ตัวใหม่ → .catch() ดักได้เหมือน reject()
// กรณี 1: reject() ใน executor
new Promise(function (resolve, reject) {
reject(new Error("จาก reject"));
}).catch(function (err) {
console.log("ดักได้:", err.message);
});
// output:
// ดักได้: จาก reject
// กรณี 2: throw ใน .then()
Promise.resolve("ข้อมูล")
.then(function (value) {
throw new Error("เกิดข้อผิดพลาดระหว่างประมวลผล");
})
.catch(function (err) {
console.log("ดักได้:", err.message);
});
// output:
// ดักได้: เกิดข้อผิดพลาดระหว่างประมวลผลสรุป: `.catch()` ดัก error ได้ 2 แหล่ง: - **`reject(error)`** ใน executor หรือ Promise ตัวก่อนหน้า - **`throw error`** ใน `.then()` callback ทั้งสองแหล่งสร้าง rejected Promise → `.catch()` ดักได้เหมือนกัน
เมื่อ error เกิดขึ้นใน chain (จาก `reject()` หรือ `throw`) มันจะ **ไหลผ่าน** `.then()` ทุกตัวจนกว่าจะเจอ `.catch()` — เหมือนน้ำไหลผ่านประตูที่ปิดอยู่ **`.then()` callback ไม่ทำงานเมื่อมี error ไหลผ่าน** — มันข้ามไปเลย:
step 2 reject → .then() ของ step 2 และ step 3 ถูกข้าม → .catch() ดัก error
function step1() {
return Promise.resolve("ข้อมูลเริ่มต้น");
}
function step2() {
return Promise.reject(new Error("step 2 ล้มเหลว"));
}
function step3() {
return Promise.resolve("จบแล้ว");
}
step1()
.then(function (result) {
console.log("step 1:", result);
return step2();
})
.then(function (result) {
console.log("step 2:", result); // ไม่ทำงาน — error ไหลผ่าน
return step3();
})
.then(function (result) {
console.log("step 3:", result); // ไม่ทำงาน — error ยังไหลผ่าน
})
.catch(function (err) {
console.log("ดัก error:", err.message); // ดัก error: step 2 ล้มเหลว
});
// output:
// step 1: ข้อมูลเริ่มต้น
// ดัก error: step 2 ล้มเหลวลำดับการทำงาน: 1. `step1()` → fulfilled → `.then()` ทำงาน → log "step 1" 2. `step2()` → **rejected** → `.then()` ถัดไป **ถูกข้าม** 3. `.then()` ถัดไป **ถูกข้าม** เช่นกัน — error ยังไหลผ่าน 4. `.catch()` **ดัก error** → log "ดัก error" ถ้ามี `.catch()` หลายจุดใน chain — error จะถูกดักที่ **`.catch()` ตัวแรก** ที่เจอ:
error ถูกดักที่ .catch() ตัวแรกที่เจอ — .catch() ตัวถัดไปไม่ทำงาน
Promise.reject(new Error("error ต้นทาง"))
.catch(function (err) {
console.log("ดักที่จุด 1:", err.message); // ดักที่นี่
})
.catch(function (err) {
console.log("ดักที่จุด 2:", err.message); // ไม่ทำงาน — error ถูกดักไปแล้ว
});
// output:
// ดักที่จุด 1: error ต้นทาง`.catch()` ตัวที่สองไม่ทำงานเพราะ `.catch()` ตัวแรกดัก error ไปแล้ว — chain กลับมาเป็น `fulfilled` หลังจากจุดนั้น
error ไม่ได้ "หายไป" หลัง .catch()
ถ้า .catch() เอง throw หรือ return rejected Promise → error ใหม่จะไหลต่อไปยัง .catch() ถัดไป (ถ้ามี) — .catch() ไม่ได้ "กลืน" error เสมอไป
`.catch()` คืน **Promise ตัวใหม่** เหมือน `.then()` — ถ้า callback ใน `.catch()` return ค่าปกติ (ไม่ใช่ throw) → Promise ตัวใหม่จะเป็น `fulfilled` → chain **ทำงานต่อได้** เรียกว่า **recovery** — การ "ซ่อม" error ด้วยการคืนค่า fallback:
.catch() return ค่า fallback → Promise ตัวใหม่เป็น fulfilled → .then() ถัดไปทำงานต่อ
Promise.reject(new Error("โหลดข้อมูลไม่สำเร็จ"))
.catch(function (err) {
console.log("ดัก error:", err.message);
return "ข้อมูลสำรอง"; // recovery — คืนค่า fallback
})
.then(function (value) {
console.log("ได้ค่า:", value); // ทำงานต่อ — เพราะ .catch() คืนค่า
});
// output:
// ดัก error: โหลดข้อมูลไม่สำเร็จ
// ได้ค่า: ข้อมูลสำรองลำดับการทำงาน: 1. `Promise.reject(...)` → rejected 2. `.catch()` ดัก error → log "ดัก error" → **return "ข้อมูลสำรอง"** 3. `.catch()` คืน Promise ตัวใหม่ที่ fulfilled ด้วย "ข้อมูลสำรอง" 4. `.then()` ถัดไปทำงาน → log "ได้ค่า: ข้อมูลสำรอง" ตัวอย่างจริง — โหลดข้อมูลผู้ใช้ ถ้าล้มเหลวก็ใช้ข้อมูล guest แทน:
loadUser() reject → .catch() คืน guest user → .then() ถัดไปใช้ข้อมูลได้เลย
function loadUser(isOnline) {
return new Promise(function (resolve, reject) {
if (isOnline) {
resolve({ name: "Mali", role: "member" });
} else {
reject(new Error("ไม่มีการเชื่อมต่อ"));
}
});
}
var guestUser = { name: "Guest", role: "guest" };
loadUser(false) // สมมติว่า offline
.then(function (user) {
console.log("ยินดีต้อนรับ", user.name);
return user;
})
.catch(function (err) {
console.log("ดัก error:", err.message);
return guestUser; // recovery — ใช้ guest user แทน
})
.then(function (user) {
console.log("แสดงผล:", user.name, "(" + user.role + ")");
});
// output:
// ดัก error: ไม่มีการเชื่อมต่อ
// แสดงผล: Guest (guest)ถ้า `loadUser(true)` → `.then()` ทำงาน → `.catch()` ข้าม → `.then()` สุดท้ายได้ user จริง ถ้า `loadUser(false)` → `.catch()` ดัก error → return guestUser → `.then()` สุดท้ายได้ guest **ทั้งสองกรณี `.then()` สุดท้ายทำงานเสมอ** — เพราะ `.catch()` recovery แล้ว
Recovery ไม่ใช่การ "ซ่อน" error
Recovery คือการให้ทางเลือกเมื่องานหลักล้มเหลว — ใช้เมื่อมี fallback ที่เหมาะสมเท่านั้น ถ้า error นั้นไม่มีทางเลือกสำรอง ก็ไม่ควร recovery ให้ error ไหลไป .catch() ด้านนอกแทน
.catch() ที่ไม่ return ค่า → Promise ตัวใหม่ fulfilled ด้วย undefined → .then() ถัดไปได้ undefined
Promise.reject(new Error("ล้มเหลว"))
.catch(function (err) {
console.log("ดัก error:", err.message);
// ไม่ return อะไร
})
.then(function (value) {
console.log("ได้ค่า:", value); // ได้ค่า: undefined
});
// output:
// ดัก error: ล้มเหลว
// ได้ค่า: undefinedถ้า `.catch()` ไม่ return ค่า → JavaScript return `undefined` โดยปริยาย → `.then()` ถัดไปได้ `undefined` นี่ไม่ใช่ recovery ที่แท้จริง — เพราะ `.then()` ถัดไปไม่มีค่าที่ใช้งานได้ **ถ้าต้องการ recovery ต้อง return ค่า fallback อย่างชัดเจน**
**`.finally(callback)`** ใช้ลงทะเบียน callback ที่จะถูกเรียกเมื่อ Promise **settle** — คือไม่ว่าจะ `fulfilled` หรือ `rejected` ก็ตาม callback จะทำงานเสมอ เหมาะสำหรับ **cleanup** — งานที่ต้องทำไม่ว่าผลลัพธ์จะออกมาแค่ไหน เช่น: - ซ่อน loading spinner - ปิดการเชื่อมต่อ - reset สถานะ
.then() ทำงานเมื่อ fulfilled, .catch() ทำงานเมื่อ rejected, .finally() ทำงานเสมอ
// กรณี 1: สำเร็จ
Promise.resolve("ข้อมูล")
.then(function (value) {
console.log("then:", value);
})
.catch(function (err) {
console.log("catch:", err.message);
})
.finally(function () {
console.log("finally: ทำความสะอาด");
});
// output:
// then: ข้อมูล
// finally: ทำความสะอาด
// กรณี 2: ล้มเหลว
Promise.reject(new Error("ล้มเหลว"))
.then(function (value) {
console.log("then:", value);
})
.catch(function (err) {
console.log("catch:", err.message);
})
.finally(function () {
console.log("finally: ทำความสะอาด");
});
// output:
// catch: ล้มเหลว
// finally: ทำความสะอาด`.finally()` มี 3 คุณสมบัติสำคัญ: 1. **ไม่รับ argument** — callback ใน `.finally()` ไม่ได้รับค่าหรือ error — มันไม่รู้ว่า Promise สำเร็จหรือล้มเหลว (ต่างจาก `.then()` ที่รับ value, `.catch()` ที่รับ error) 2. **ส่งค่าผ่านต่อ** — ค่าจาก `.then()` หรือ error จาก `.catch()` จะ **ไหลผ่าน** `.finally()` ไปยัง chain ถัดไปโดยไม่เปลี่ยนแปลง 3. **ทำงานเสมอ** — ไม่ว่าจะสำเร็จ, ล้มเหลว, หรือ recovery — `.finally()` ทำงานทุกกรณี
isLoading = true → ทำงาน async → isLoading = false ใน .finally() ไม่ว่าจะสำเร็จหรือล้มเหลว
var isLoading = true;
var data = null;
function fetchProfile(isSuccess) {
return new Promise(function (resolve, reject) {
if (isSuccess) {
resolve({ name: "Mali", level: 5 });
} else {
reject(new Error("network error"));
}
});
}
fetchProfile(true) // ลองเปลี่ยนเป็น false ดู
.then(function (profile) {
data = profile;
console.log("โหลดสำเร็จ:", profile.name);
})
.catch(function (err) {
console.log("โหลดล้มเหลว:", err.message);
})
.finally(function () {
isLoading = false;
console.log("isLoading =", isLoading);
});
// เมื่อ fetchProfile(true):
// โหลดสำเร็จ: Mali
// isLoading = false
// เมื่อ fetchProfile(false):
// โหลดล้มเหลว: network error
// isLoading = falseสังเกต: `isLoading = false` ใน `.finally()` ทำงานทั้งสองกรณี — ไม่ต้องเขียนซ้ำใน `.then()` และ `.catch()` ถ้าไม่มี `.finally()` คุณต้องเขียน cleanup ในทั้งสองที่:
ถ้าไม่ใช้ .finally() ต้องเขียน isLoading = false ทั้งใน .then() และ .catch()
var isLoading = true;
fetchProfile(true)
.then(function (profile) {
console.log("โหลดสำเร็จ:", profile.name);
isLoading = false; // cleanup ที่ 1
})
.catch(function (err) {
console.log("โหลดล้มเหลว:", err.message);
isLoading = false; // cleanup ที่ 2 — ซ้ำ!
});ค่าจาก .then() ไหลผ่าน .finally() ไปยัง .then() ถัดไปโดยไม่เปลี่ยนแปลง
Promise.resolve(42)
.then(function (n) {
return n * 2; // ส่ง 84 ต่อ
})
.finally(function () {
console.log("ทำ cleanup"); // ไม่รับค่า ไม่ส่งผลต่อ
})
.then(function (value) {
console.log("ได้ค่า:", value); // ได้ค่า: 84 — ค่าเดิมจาก .then()
});
// output:
// ทำ cleanup
// ได้ค่า: 84`.finally()` ไม่รับ argument และไม่เปลี่ยนค่าที่ไหลผ่าน — `42 * 2 = 84` จาก `.then()` ไหลผ่าน `.finally()` ไปยัง `.then()` ถัดไปโดยไม่เปลี่ยนแปลง
อย่าใช้ .finally() เพื่อเปลี่ยนค่า
ค่าที่ return จาก .finally() จะไม่ถูกส่งต่อไปยัง chain — .finally() มีไว้ทำ cleanup เท่านั้น ถ้าต้องการเปลี่ยนค่า ใช้ .then() หรือ .catch() แทน
ถ้า Promise reject แล้ว **ไม่มี `.catch()`** ใน chain → error นั้นไม่มีใครดัก เรียกว่า **Unhandled Promise Rejection** Browser จะแสดง warning ใน console — และใน Node.js เวอร์ชันใหม่ unhandled rejection อาจทำให้ **process หยุดทำงาน**:
Promise ที่ reject โดยไม่มี .catch() → browser แสดง UnhandledPromiseRejectionWarning
// ❌ ไม่มี .catch() — error ไม่มีใครดัก
new Promise(function (resolve, reject) {
reject(new Error("ไม่มีใครดัก error นี้"));
});
// console แสดง warning:
// UnhandledPromiseRejectionWarning: Error: ไม่มีใครดัก error นี้
// (node:12345) UnhandledPromiseRejectionWarning: Unhandled promise rejection
// ✅ แก้: ใส่ .catch()
new Promise(function (resolve, reject) {
reject(new Error("ดักแล้ว"));
}).catch(function (err) {
console.log("ดัก error:", err.message);
});
// output:
// ดัก error: ดักแล้วUnhandled rejection เกิดได้ง่ายมาก — แม้แค่ลืมใส่ `.catch()` ต่อท้าย chain:
// ❌ ลืม .catch() → unhandled rejection
fetchData()
.then(function (data) {
console.log(data);
}); // ไม่มี .catch()!**กฎเหล็ก: ทุก Promise chain ที่อาจ reject ต้องมี `.catch()`** — ไม่มีข้อยกเว้น
ทุก chain ต้องมี .catch() — ไม่มีข้อยกเว้น
แม้คุณคิดว่า Promise จะไม่ reject ก็ตาม — ถ้ามัน reject ขึ้นมาจริง ๆ โดยไม่มี .catch() คุณจะได้ warning ใน console หรือ process หยุดทำงาน
error มาจาก reject() ใน executor หรือ throw ใน .then() ก็ตาม — .catch() ดักได้ทั้งหมด
แต่ .catch() อ่านง่ายกว่า — เห็นทันทีว่าบรรทัดนี้ดัก error
.then() callback ถูกข้ามเมื่อมี error ไหลผ่าน — error หยุดเมื่อเจอ .catch() ตัวแรก
return fallback จาก .catch() → chain กลับมาเป็น fulfilled → .then() ถัดไปทำงานต่อ
ใช้เมื่อมีทางเลือกสำรองที่เหมาะสม — ถ้าไม่มี fallback ก็ไม่ควร recovery
เหมาะสำหรับ cleanup — ซ่อน loading, ปิดการเชื่อมต่อ, reset สถานะ
callback ใน .finally() ไม่รู้ว่า Promise สำเร็จหรือล้มเหลว — ต่างจาก .then() ที่รับ value และ .catch() ที่รับ error
ค่าจาก .then() หรือ error จาก .catch() ไหลผ่าน .finally() โดยไม่เปลี่ยนแปลง
Unhandled rejection แสดง warning ใน console — ใน Node.js เวอร์ชันใหม่อาจทำให้ process หยุดทำงาน
ค่าที่ return จาก .finally() จะไม่ถูกส่งต่อ — ถ้าต้องการเปลี่ยนค่า ใช้ .then() หรือ .catch()