Now.js Framework Documentation
AuthErrorHandler - การจัดการข้อผิดพลาดสำหรับการตรวจสอบสิทธิ์
AuthErrorHandler - การจัดการข้อผิดพลาดสำหรับการตรวจสอบสิทธิ์
เอกสารฉบับนี้อธิบาย AuthErrorHandler ซึ่งเป็นระบบจัดการข้อผิดพลาดสำหรับกระบวนการตรวจสอบสิทธิ์ของ Now.js Framework
📋 สารบัญ
- ภาพรวม
- การติดตั้งและนำเข้า
- การเริ่มต้นใช้งาน
- ประเภท Error
- การดำเนินการ Error
- การจัดการ Error แบบกำหนดเอง
- เหตุการณ์ Error
- ตรรกะการลองใหม่
- การกู้คืนจาก Error
- ตัวอย่างการใช้งาน
- เอกสารอ้างอิง API
- แนวทางปฏิบัติที่ดี
- ข้อผิดพลาดที่พบบ่อย
ภาพรวม
AuthErrorHandler จัดการข้อผิดพลาด และข้อยกเว้น ที่เกิดขึ้นในกระบวนการตรวจสอบสิทธิ์ พร้อมส่งต่อการตอบสนองและกระบวนการกู้คืนอย่างเหมาะสม
ฟีเจอร์หลัก
- ✅ การจัดหมวดหมู่ข้อผิดพลาด: แยกประเภทข้อผิดพลาดอย่างชัดเจน
- ✅ การกำหนดการทำงาน: กำหนดลำดับการตอบสนองสำหรับแต่ละข้อผิดพลาดได้เอง
- ✅ ตัวจัดการแบบกำหนดเอง: รองรับการต่อยอดด้วยตัวจัดการข้อผิดพลาดเพิ่มเติม
- ✅ ตรรกะการลองใหม่: ลองใหม่อัตโนมัติสำหรับข้อผิดพลาดที่รองรับ
- ✅ กลไกการกู้คืน: มีกระบวนการกู้คืนเมื่อเกิดข้อผิดพลาดร้ายแรง
- ✅ ระบบเหตุการณ์: กระจายเหตุการณ์สำหรับการติดตามและบันทึก
- ✅ การแจ้งเตือนผู้ใช้: สื่อสารสถานะข้อผิดพลาดให้ผู้ใช้ทราบทันที
- ✅ การบันทึกข้อผิดพลาด: เก็บรายละเอียดข้อผิดพลาดเพื่อใช้ดีบักและวิเคราะห์
- ✅ การลดทอนฟีเจอร์อย่างสุภาพ: ลดฟังก์ชันที่ไม่สำคัญเพื่อให้ระบบทำงานต่อได้
- ✅ ขอบเขตข้อผิดพลาด: จำกัดไม่ให้ข้อผิดพลาดลุกลามกระทบส่วนอื่น
เมื่อไหร่ควรใช้ AuthErrorHandler
✅ ใช้ AuthErrorHandler เมื่อ:
- ต้องการระบบจัดการข้อผิดพลาดในการตรวจสอบสิทธิ์แบบครบวงจร
- ต้องการตรรกะการจัดการข้อผิดพลาดที่ปรับแต่งเองได้
- ต้องการติดตามและบันทึกข้อผิดพลาดอย่างเป็นระบบ
- ต้องการตรรกะการลองใหม่สำหรับข้อผิดพลาดที่เกิดซ้ำได้
- ต้องการแจ้งเตือนผู้ใช้ทันทีเมื่อเกิดข้อผิดพลาดสำคัญ
❌ ไม่ควรใช้เมื่อ:
- ต้องการจัดการข้อผิดพลาดด้วยโค้ดแบบแมนนวลทั้งหมด
- แอปพลิเคชันไม่ได้ใช้ระบบตรวจสอบสิทธิ์ของ Now.js
การติดตั้งและนำเข้า
AuthErrorHandler โหลดมาพร้อมกับ Now.js Framework และพร้อมใช้งานทันทีผ่าน window object:
// ไม่ต้อง import - พร้อมใช้งานทันที
console.log(window.AuthErrorHandler); // อ็อบเจ็กต์ AuthErrorHandler ที่ผูกไว้กับ windowการเริ่มต้นใช้งาน
การตั้งค่าพื้นฐาน
// AuthErrorHandler ทำงานร่วมกับ AuthManager โดยอัตโนมัติ
// ไม่จำเป็นต้องมีขั้นตอนเริ่มต้นแยกต่างหาก
await AuthManager.init({
enabled: true,
endpoints: {
login: '/api/auth/login',
verify: '/api/auth/verify'
},
// การกำหนดค่าการจัดการข้อผิดพลาด
errorHandling: {
// จำนวนครั้งสูงสุดในการลองใหม่
maxRetries: 3,
// ระยะเวลาหน่วงก่อนลองใหม่ (มิลลิวินาที)
retryDelay: 1000,
// แสดงการแจ้งเตือนให้ผู้ใช้
showNotifications: true,
// บันทึกข้อผิดพลาดลงคอนโซล
logErrors: true
}
});
console.log('AuthErrorHandler เริ่มทำงานร่วมกับ AuthManager แล้ว');Error Handling Flow
การดำเนินการล้มเหลว
↓
┌────────────────────┐
│ จำแนกข้อผิดพลาด │
│ - เครือข่ายหรือไม่? │
│ - การตรวจสอบสิทธิ์หรือไม่? │
│ - การอนุญาตหรือไม่? │
│ - การตรวจสอบข้อมูลหรือไม่? │
└──────┬─────────────┘
↓
┌────────────────────┐
│ เลือกการดำเนินการ │
│ - ลองใหม่หรือไม่? │
│ - เปลี่ยนเส้นทางหรือไม่? │
│ - แจ้งเตือนหรือไม่? │
│ - บันทึกหรือไม่? │
└──────┬─────────────┘
↓
┌────────────────────┐
│ ปฏิบัติตามแผนที่เลือก │
│ - ลองทำงานซ้ำ │
│ - เปลี่ยนเส้นทาง │
│ - แสดงข้อความ │
│ - บันทึกข้อผิดพลาด │
└──────┬─────────────┘
↓
┌────────────────────┐
│ ส่งเหตุการณ์ข้อผิดพลาด │
│ - เรียกตัวจัดการแบบกำหนดเอง │
│ - บันทึกเพื่อติดตาม │
│ - เก็บ log เพิ่มเติม │
└────────────────────┘ประเภท Error
AuthErrorHandler รองรับ error types หลักๆ:
1. NETWORK_ERROR
การเชื่อมต่อเครือข่ายล้มเหลว
// เกิดขึ้นเมื่อ:
// - ไม่มีการเชื่อมต่ออินเทอร์เน็ต
// - ไม่สามารถเข้าถึงเซิร์ฟเวอร์ได้
// - คำขอหมดเวลา
// - ถูกปฏิเสธโดยนโยบาย CORS
// การดำเนินการเริ่มต้น: ลองใหม่ด้วยการหน่วงแบบยกกำลัง
{
type: 'NETWORK_ERROR',
message: 'Network connection failed',
retryable: true,
action: 'retry'
}2. UNAUTHORIZED
ยังไม่ได้รับการตรวจสอบสิทธิ์หรือโทเค็นหมดอายุ
// เกิดขึ้นเมื่อ:
// - ไม่มีโทเค็นสำหรับพิสูจน์ตัวตน
// - โทเค็นหมดอายุ
// - โทเค็นไม่ถูกต้อง
// - โทเค็นถูกเพิกถอน
// การดำเนินการเริ่มต้น: เปลี่ยนเส้นทางไปหน้าลงชื่อเข้าใช้
{
type: 'UNAUTHORIZED',
message: 'Authentication required',
retryable: false,
action: 'redirect',
target: '/login'
}3. FORBIDDEN
ตรวจสอบสิทธิ์แล้วแต่ไม่มีสิทธิ์เข้าถึง
// เกิดขึ้นเมื่อ:
// - ไม่มีบทบาทที่ต้องการ
// - ไม่มีสิทธิ์ที่กำหนดไว้
// - การเข้าถึงถูกปฏิเสธโดยนโยบายความปลอดภัย
// การดำเนินการเริ่มต้น: แสดงหน้าแจ้งข้อผิดพลาด
{
type: 'FORBIDDEN',
message: 'Access denied',
retryable: false,
action: 'render',
target: '/403'
}4. VALIDATION_ERROR
ข้อมูลไม่ผ่านการตรวจสอบความถูกต้อง
// เกิดขึ้นเมื่อ:
// - ข้อมูลประจำตัวไม่ถูกต้อง
// - ขาดฟิลด์ที่จำเป็น
// - รูปแบบข้อมูลไม่ตรงตามที่กำหนด
// การดำเนินการเริ่มต้น: แสดงรายละเอียดข้อผิดพลาดให้ผู้ใช้ทราบ
{
type: 'VALIDATION_ERROR',
message: 'Invalid input',
errors: {
email: 'Invalid email format',
password: 'Password too short'
},
retryable: true,
action: 'notify'
}5. TOKEN_EXPIRED
โทเค็นหมดอายุ
// เกิดขึ้นเมื่อ:
// - โทเค็นสำหรับเข้าถึง หมดอายุ
// - โทเค็นสำหรับรีเฟรช หมดอายุ
// การดำเนินการเริ่มต้น: พยายามรีเฟรชโทเค็น แล้วค่อยเปลี่ยนเส้นทางถ้าจำเป็น
{
type: 'TOKEN_EXPIRED',
message: 'Token expired',
retryable: true,
action: 'refresh',
fallback: 'redirect',
target: '/login'
}6. TOKEN_REFRESH_FAILED
การรีเฟรชโทเค็นล้มเหลว
// เกิดขึ้นเมื่อ:
// - โทเค็นสำหรับรีเฟรชไม่ถูกต้อง
// - ปลายทางสำหรับรีเฟรชตอบกลับข้อผิดพลาด
// - ไม่มีโทเค็นสำหรับรีเฟรช
// การดำเนินการเริ่มต้น: เปลี่ยนเส้นทางไปหน้าลงชื่อเข้าใช้ใหม่
{
type: 'TOKEN_REFRESH_FAILED',
message: 'Failed to refresh token',
retryable: false,
action: 'redirect',
target: '/login'
}7. SESSION_EXPIRED
เซสชันหมดอายุ
// เกิดขึ้นเมื่อ:
// - เซสชันหมดเวลา
// - เซสชันถูกยกเลิกด้วยเหตุผลด้านความปลอดภัย
// - มีการออกจากระบบจากอุปกรณ์อื่น
// การดำเนินการเริ่มต้น: เปลี่ยนเส้นทางไปหน้าลงชื่อเข้าใช้ พร้อมแจ้งผู้ใช้
{
type: 'SESSION_EXPIRED',
message: 'Your session has expired',
retryable: false,
action: 'redirect',
target: '/login',
notify: true
}8. RATE_LIMIT_EXCEEDED
ส่งคำขอเกินขีดจำกัด
// เกิดขึ้นเมื่อ:
// - มีคำขอต่อเนื่องมากเกินไป
// - เกินขีดจำกัดที่เซิร์ฟเวอร์ตั้งไว้
// การดำเนินการเริ่มต้น: หน่วงเวลาตามที่ระบบแนะนำก่อนลองใหม่อีกครั้ง
{
type: 'RATE_LIMIT_EXCEEDED',
message: 'Too many requests',
retryable: true,
action: 'retry',
retryAfter: 60000, // 1 นาที
notify: true
}9. SERVER_ERROR
ข้อผิดพลาดภายในเซิร์ฟเวอร์
// เกิดขึ้นเมื่อ:
// - เซิร์ฟเวอร์ส่งสถานะ 500
// - มีข้อยกเว้นเกิดขึ้นในฝั่งเซิร์ฟเวอร์
// - ระบบฐานข้อมูลตอบสนองผิดพลาด
// การดำเนินการเริ่มต้น: ลองใหม่จำนวนจำกัดพร้อมการหน่วงเวลา
{
type: 'SERVER_ERROR',
message: 'Server error occurred',
retryable: true,
action: 'retry',
maxRetries: 3
}10. CSRF_ERROR
โทเค็น CSRF ไม่ถูกต้อง
// เกิดขึ้นเมื่อ:
// - ไม่มีโทเค็น CSRF แนบมากับคำขอ
// - โทเค็น CSRF ไม่ตรงกับที่ระบบคาดหวัง
// - โทเค็น CSRF หมดอายุ
// การดำเนินการเริ่มต้น: รีเฟรชโทเค็น CSRF แล้วลองทำงานใหม่อีกครั้ง
{
type: 'CSRF_ERROR',
message: 'CSRF validation failed',
retryable: true,
action: 'refresh_csrf',
fallback: 'reload'
}11. CUSTOM_ERROR
ข้อผิดพลาดที่นิยามเอง
// เกิดจากตรรกะของแอปพลิเคชันที่กำหนดขึ้นมาเอง
{
type: 'CUSTOM_ERROR',
code: 'SUBSCRIPTION_REQUIRED',
message: 'ต้องมีการสมัครสมาชิกที่ยังใช้งานอยู่',
retryable: false,
action: 'custom',
handler: 'handleSubscriptionError'
}การดำเนินการ Error
1. การลองใหม่
// ลองทำงานที่ล้มเหลวอีกครั้งโดยอัตโนมัติ
{
action: 'retry',
maxRetries: 3,
retryDelay: 1000,
backoff: 'exponential' // linear (เส้นตรง), exponential (ยกกำลัง), fixed (คงที่)
}
// ลองใหม่ด้วยการหน่วงแบบยกกำลัง
// ครั้งที่ 1: หน่วง 1 วินาที
// ครั้งที่ 2: หน่วง 2 วินาที
// ครั้งที่ 3: หน่วง 4 วินาที2. การเปลี่ยนเส้นทาง
// เปลี่ยนเส้นทางไปยัง route อื่น
{
action: 'redirect',
target: '/login',
reason: 'Authentication required',
storeIntendedRoute: true // เก็บเส้นทางปัจจุบันไว้ชั่วคราว
}
// หลังจากลงชื่อเข้าใช้ เปลี่ยนเส้นทางกลับไปยังหน้าที่ต้องการ3. การแสดงผล
// แสดงหน้าข้อผิดพลาด
{
action: 'render',
target: '/403',
context: {
error: 'Access denied',
requiredRole: 'admin'
}
}4. การแจ้งเตือน
// แสดงการแจ้งเตือนให้ผู้ใช้
{
action: 'notify',
notification: {
type: 'error',
title: 'Login Failed',
message: 'Invalid credentials',
duration: 5000
}
}5. การบล็อก
// บล็อกการนำทาง อยู่ในหน้าปัจจุบัน
{
action: 'block',
reason: 'Unsaved changes',
confirm: true // แสดงกล่องยืนยันก่อนดำเนินการต่อ
}6. การรีเฟรช
// รีเฟรชโทเค็นชุดปัจจุบัน
{
action: 'refresh',
type: 'token',
fallback: {
action: 'redirect',
target: '/login'
}
}7. การออกจากระบบ
// บังคับออกจากระบบ
{
action: 'logout',
reason: 'Session invalidated',
redirect: '/login',
notify: true
}8. การดำเนินการแบบกำหนดเอง
// ดำเนินการ handler แบบกำหนดเอง
{
action: 'custom',
handler: async (error, context) => {
console.log('Custom error handler:', error);
// ตรรกะแบบกำหนดเอง
if (error.code === 'PAYMENT_REQUIRED') {
await showPaymentModal();
}
return { handled: true };
}
}การจัดการ Error แบบกำหนดเอง
1. ตัวจัดการ Error ทั่วไป
// ตั้งค่าตัวจัดการข้อผิดพลาดระดับทั่วทั้งระบบ
AuthErrorHandler.setGlobalHandler(async (error, context) => {
console.log('Global error handler:', error.type);
// บันทึกไปยัง external service
await logErrorToService(error);
// การแจ้งเตือนแบบกำหนดเอง
if (error.type === 'UNAUTHORIZED') {
showCustomLoginModal();
return { handled: true };
}
// ให้ default handler ดำเนินการต่อ
return { handled: false };
});2. ตัวจัดการสำหรับ Error แต่ละประเภท
// ลงทะเบียนตัวจัดการสำหรับข้อผิดพลาดแต่ละประเภท
AuthErrorHandler.registerHandler('VALIDATION_ERROR', async (error) => {
console.log('Validation errors:', error.errors);
// แสดงข้อความข้อผิดพลาดในฟอร์ม
Object.keys(error.errors).forEach(field => {
showFieldError(field, error.errors[field]);
});
return { handled: true };
});
// Handler สำหรับ rate limiting
AuthErrorHandler.registerHandler('RATE_LIMIT_EXCEEDED', async (error) => {
const retryAfter = error.retryAfter || 60000;
const minutes = Math.ceil(retryAfter / 60000);
showNotification(
`Too many attempts. Please try again in ${minutes} minute(s).`,
'warning'
);
return { handled: true };
});3. การจัดการ Error ตาม Context
// จัดการข้อผิดพลาดตามบริบท
AuthErrorHandler.setContextHandler(async (error, context) => {
console.log('Context:', context);
// การจัดการที่แตกต่างกันสำหรับแต่ละการดำเนินการ
if (context.operation === 'login') {
if (error.type === 'VALIDATION_ERROR') {
highlightInvalidFields(error.errors);
return { handled: true };
}
}
if (context.operation === 'refresh_token') {
if (error.type === 'TOKEN_REFRESH_FAILED') {
// บังคับออกจากระบบเมื่อ refresh ล้มเหลว
await AuthManager.logout();
return { handled: true };
}
}
return { handled: false };
});4. ตัวจัดการ Error แบบลูกโซ่
// หลาย handlers ที่ทำงานต่อเนื่อง
AuthErrorHandler.chainHandlers([
// 1. บันทึกข้อผิดพลาดทั้งหมด
async (error) => {
await logError(error);
return { handled: false }; // ดำเนินการต่อในลูกโซ่
},
// 2. ติดตามใน analytics
async (error) => {
trackErrorInAnalytics(error);
return { handled: false }; // ดำเนินการต่อในลูกโซ่
},
// 3. การจัดการแบบกำหนดเอง
async (error) => {
if (error.type === 'NETWORK_ERROR') {
showOfflineMode();
return { handled: true }; // หยุดลูกโซ่
}
return { handled: false };
},
// 4. Default handler (เฉพาะถ้ายังไม่ได้จัดการ)
async (error) => {
showGenericErrorMessage(error);
return { handled: true };
}
]);เหตุการณ์ Error
เหตุการณ์ที่ใช้ได้
// รับฟังเหตุการณ์เกี่ยวกับข้อผิดพลาด
// 1. ข้อผิดพลาดทุกประเภท
document.addEventListener('auth:error', (e) => {
const { error, context } = e.detail;
console.log('Auth error:', error.type);
});
// 2. ข้อผิดพลาดแบบเฉพาะเจาะจง
document.addEventListener('auth:error:unauthorized', (e) => {
console.log('User unauthorized');
});
document.addEventListener('auth:error:forbidden', (e) => {
console.log('Access forbidden');
});
// 3. ข้อผิดพลาดด้านเครือข่าย
document.addEventListener('auth:error:network', (e) => {
console.log('Network error');
showOfflineIndicator();
});
// 4. ข้อผิดพลาดจากการตรวจสอบข้อมูล
document.addEventListener('auth:error:validation', (e) => {
const { errors } = e.detail;
console.log('Validation errors:', errors);
});
// 5. ข้อผิดพลาดเกี่ยวกับโทเค็น
document.addEventListener('auth:error:token', (e) => {
console.log('Token error');
});
// 6. ข้อผิดพลาดของเซสชัน
document.addEventListener('auth:error:session', (e) => {
console.log('Session error');
});
// 7. ข้อผิดพลาดจากการจำกัดอัตรา (Rate Limit)
document.addEventListener('auth:error:ratelimit', (e) => {
const { retryAfter } = e.detail;
console.log(`Rate limited. Retry after ${retryAfter}ms`);
});
// 8. ข้อผิดพลาดจากเซิร์ฟเวอร์
document.addEventListener('auth:error:server', (e) => {
console.log('Server error');
});
// 9. การกู้คืนหลังเกิดข้อผิดพลาด
document.addEventListener('auth:error:recovered', (e) => {
console.log('Error recovered');
});
// 10. การลองใหม่เมื่อมีข้อผิดพลาด
document.addEventListener('auth:error:retry', (e) => {
const { attempt, maxRetries } = e.detail;
console.log(`Retry attempt ${attempt}/${maxRetries}`);
});ตรรกะการลองใหม่
1. Automatic Retry (การลองใหม่อัตโนมัติ)
// กำหนดค่าพฤติกรรมการลองใหม่
await AuthManager.init({
errorHandling: {
maxRetries: 3,
retryDelay: 1000,
retryBackoff: 'exponential', // linear, exponential, fixed
// รายการข้อผิดพลาดที่ต้องการให้ลองใหม่
retryableErrors: [
'NETWORK_ERROR',
'SERVER_ERROR',
'TOKEN_EXPIRED',
'RATE_LIMIT_EXCEEDED'
]
}
});2. กลยุทธ์การลองใหม่
การเพิ่มแบบยกกำลัง
// ความหน่วงเพิ่มขึ้นแบบยกกำลัง
// ครั้งที่ 1: 1 วินาที
// ครั้งที่ 2: 2 วินาที
// ครั้งที่ 3: 4 วินาที
// ครั้งที่ 4: 8 วินาที
{
retryBackoff: 'exponential',
retryDelay: 1000,
maxRetries: 4
}การเพิ่มแบบเส้นตรง
// ความหน่วงเพิ่มขึ้นแบบเส้นตรง
// ครั้งที่ 1: 1 วินาที
// ครั้งที่ 2: 2 วินาที
// ครั้งที่ 3: 3 วินาที
// ครั้งที่ 4: 4 วินาที
{
retryBackoff: 'linear',
retryDelay: 1000,
maxRetries: 4
}คงที่
// ความหน่วงเท่ากันทุกครั้ง
// ครั้งที่ 1: 1 วินาที
// ครั้งที่ 2: 1 วินาที
// ครั้งที่ 3: 1 วินาที
{
retryBackoff: 'fixed',
retryDelay: 1000,
maxRetries: 3
}3. ตรรกะการลองใหม่แบบกำหนดเอง
// ตัดสินใจการลองใหม่แบบกำหนดเอง
AuthErrorHandler.setRetryDecider(async (error, attempt, context) => {
console.log(`Retry decision for ${error.type}, attempt ${attempt}`);
// ตรรกะแบบกำหนดเอง
if (error.type === 'NETWORK_ERROR') {
// ตรวจสอบว่าออนไลน์หรือไม่
if (!navigator.onLine) {
return { retry: false, reason: 'Offline' };
}
// ลองใหม่สูงสุด 5 ครั้งสำหรับข้อผิดพลาดด้านเครือข่าย
if (attempt < 5) {
return {
retry: true,
delay: attempt * 2000 // ความหน่วงแบบกำหนดเอง
};
}
}
if (error.type === 'RATE_LIMIT_EXCEEDED') {
// ใช้ retry-after header จากเซิร์ฟเวอร์
return {
retry: true,
delay: error.retryAfter || 60000
};
}
// พฤติกรรมเริ่มต้น
return { retry: false };
});4. การลองใหม่พร้อม Circuit Breaker
// ป้องกันการลองใหม่มากเกินไป
const circuitBreaker = {
failures: 0,
threshold: 5,
timeout: 60000,
state: 'CLOSED' // CLOSED, OPEN, HALF_OPEN
};
AuthErrorHandler.setRetryDecider(async (error, attempt) => {
// ตรวจสอบสถานะ circuit breaker
if (circuitBreaker.state === 'OPEN') {
const now = Date.now();
const timeSinceOpen = now - circuitBreaker.openedAt;
if (timeSinceOpen >= circuitBreaker.timeout) {
// ลอง half-open
circuitBreaker.state = 'HALF_OPEN';
} else {
// ยังเปิดอยู่ ไม่ลองใหม่
return {
retry: false,
reason: 'Circuit breaker open'
};
}
}
// ตรรกะการลองใหม่
if (error.type === 'SERVER_ERROR') {
circuitBreaker.failures++;
if (circuitBreaker.failures >= circuitBreaker.threshold) {
// เปิด circuit
circuitBreaker.state = 'OPEN';
circuitBreaker.openedAt = Date.now();
return { retry: false, reason: 'Circuit breaker opened' };
}
return { retry: true, delay: 1000 };
}
return { retry: false };
});
// รีเซ็ตเมื่อสำเร็จ
document.addEventListener('auth:success', () => {
if (circuitBreaker.state === 'HALF_OPEN') {
circuitBreaker.state = 'CLOSED';
circuitBreaker.failures = 0;
}
});การกู้คืนจาก Error
1. การกู้คืนอัตโนมัติ
// AuthErrorHandler พยายามกู้คืนโดยอัตโนมัติ
// สำหรับ TOKEN_EXPIRED:
// 1. พยายาม refresh token
// 2. ถ้า refresh ล้มเหลว เปลี่ยนเส้นทางไปหน้า login
// สำหรับข้อผิดพลาดด้านเครือข่าย (NETWORK_ERROR):
// 1. ลองใหม่ด้วย backoff
// 2. แสดงตัวบ่งชี้ออฟไลน์
// 3. จัดคิว operations สำหรับเมื่อออนไลน์
// สำหรับ SESSION_EXPIRED:
// 1. ล้างข้อมูล session
// 2. เก็บ route ที่ต้องการ
// 3. เปลี่ยนเส้นทางไปหน้า login2. กลยุทธ์การกู้คืนแบบกำหนดเอง
// ลงทะเบียนกลยุทธ์การกู้คืน
AuthErrorHandler.registerRecovery('TOKEN_EXPIRED', async (error, context) => {
console.log('Recovering from token expiration');
try {
// พยายาม refresh token
await AuthManager.refreshToken();
// ลองทำ operation เดิมอีกครั้ง
if (context.operation) {
await context.operation.retry();
}
return { recovered: true };
} catch (refreshError) {
// Refresh ล้มเหลว เปลี่ยนเส้นทางไปหน้า login
await Router.navigate('/login');
return { recovered: false };
}
});3. การลดทอนฟีเจอร์อย่างสง่างาม
// ลดทอนฟีเจอร์แทนที่จะล้มเหลวทั้งหมด
AuthErrorHandler.registerRecovery('NETWORK_ERROR', async (error) => {
console.log('Network error - enabling offline mode');
// เปิดใช้งาน offline mode
app.offlineMode = true;
// แสดงตัวบ่งชี้ออฟไลน์
showOfflineBanner();
// ใช้ข้อมูลที่แคชไว้
const cachedData = await loadFromCache();
if (cachedData) {
renderWithCachedData(cachedData);
return { recovered: true };
}
return { recovered: false };
});
// กู้คืนเมื่อออนไลน์
window.addEventListener('online', async () => {
if (app.offlineMode) {
app.offlineMode = false;
hideOfflineBanner();
// ซิงค์การเปลี่ยนแปลงออฟไลน์
await syncOfflineChanges();
// โหลดหน้าปัจจุบันใหม่
await Router.reload();
}
});ตัวอย่างการใช้งาน
1. การตั้งค่าการจัดการ Error แบบสมบูรณ์
// เริ่มต้นด้วยการจัดการข้อผิดพลาดแบบครอบคลุม
await AuthManager.init({
enabled: true,
endpoints: {
login: '/api/auth/login',
logout: '/api/auth/logout',
refresh: '/api/auth/refresh',
verify: '/api/auth/verify'
},
errorHandling: {
// การกำหนดค่าการลองใหม่
maxRetries: 3,
retryDelay: 1000,
retryBackoff: 'exponential',
// การกำหนดค่าการแจ้งเตือน
showNotifications: true,
notificationDuration: 5000,
// การบันทึก
logErrors: true,
logLevel: 'error',
// ประเภทข้อผิดพลาดที่ต้องการให้ลองใหม่
retryableErrors: [
'NETWORK_ERROR',
'SERVER_ERROR',
'TOKEN_EXPIRED',
'RATE_LIMIT_EXCEEDED'
]
}
});
// รับฟังข้อผิดพลาดทั้งหมด
document.addEventListener('auth:error', (e) => {
const { error, context } = e.detail;
// บันทึกไปยัง analytics
logErrorToAnalytics({
type: error.type,
message: error.message,
context: context.operation,
timestamp: new Date().toISOString()
});
});
// จัดการข้อผิดพลาดด้านเครือข่าย
document.addEventListener('auth:error:network', () => {
showOfflineIndicator();
});
// จัดการกรณีผู้ใช้ไม่ได้รับอนุญาต
document.addEventListener('auth:error:unauthorized', () => {
// ล้างสถานะ auth ที่แคชไว้
localStorage.removeItem('authState');
});
console.log('ตั้งค่าการจัดการข้อผิดพลาดเรียบร้อยแล้ว');2. การแสดง Validation Error แบบกำหนดเอง
// ลงทะเบียนตัวจัดการสำหรับข้อผิดพลาดจากการตรวจสอบข้อมูล
AuthErrorHandler.registerHandler('VALIDATION_ERROR', async (error) => {
console.log('ข้อผิดพลาดจากการตรวจสอบข้อมูล:', error.errors);
// ล้างข้อความข้อผิดพลาดก่อนหน้า
document.querySelectorAll('.error-message').forEach(el => {
el.remove();
});
document.querySelectorAll('.is-invalid').forEach(el => {
el.classList.remove('is-invalid');
});
// แสดงข้อความข้อผิดพลาดชุดใหม่
Object.keys(error.errors).forEach(field => {
const input = document.querySelector(`[name="${field}"]`);
if (input) {
// ทำเครื่องหมาย input ว่าไม่ถูกต้อง
input.classList.add('is-invalid');
// สร้างข้อความข้อผิดพลาด
const errorDiv = document.createElement('div');
errorDiv.className = 'error-message text-danger';
errorDiv.textContent = error.errors[field];
// แทรกหลัง input
input.parentNode.insertBefore(errorDiv, input.nextSibling);
}
});
// โฟกัสฟิลด์แรกที่ไม่ถูกต้อง
const firstInvalid = document.querySelector('.is-invalid');
if (firstInvalid) {
firstInvalid.focus();
}
return { handled: true };
});
// ตัวอย่าง login พร้อม validation
async function handleLogin(e) {
e.preventDefault();
try {
await AuthManager.login({
email: emailInput.value,
password: passwordInput.value
});
// สำเร็จ - เปลี่ยนเส้นทาง
await Router.navigate('/dashboard');
} catch (error) {
// AuthErrorHandler จัดการข้อผิดพลาดจากการตรวจสอบข้อมูลให้อัตโนมัติ
console.log('Login failed:', error.type);
}
}3. Rate Limiting พร้อม User Feedback
// จัดการข้อผิดพลาดจากการจำกัดอัตราการส่งคำขอ
AuthErrorHandler.registerHandler('RATE_LIMIT_EXCEEDED', async (error) => {
const retryAfter = error.retryAfter || 60000;
const minutes = Math.ceil(retryAfter / 60000);
// แสดง countdown modal
const modal = createModal({
title: 'Too Many Attempts',
message: `You've made too many login attempts. Please wait ${minutes} minute(s).`,
countdown: retryAfter,
cancelable: false
});
// เริ่ม countdown
let remaining = retryAfter;
const interval = setInterval(() => {
remaining -= 1000;
if (remaining <= 0) {
clearInterval(interval);
modal.close();
return;
}
const secs = Math.ceil(remaining / 1000);
modal.updateMessage(`Please wait ${secs} seconds...`);
}, 1000);
return { handled: true };
});
// ป้องกันการส่งฟอร์มระหว่าง rate limit
document.addEventListener('auth:error:ratelimit', (e) => {
const { retryAfter } = e.detail;
// ปิดการใช้งานฟอร์ม login
const loginForm = document.getElementById('loginForm');
const submitBtn = loginForm.querySelector('[type="submit"]');
submitBtn.disabled = true;
submitBtn.textContent = 'Please wait...';
// เปิดใช้งานอีกครั้งหลังจากหน่วงเวลา
setTimeout(() => {
submitBtn.disabled = false;
submitBtn.textContent = 'Login';
}, retryAfter);
});4. Offline Mode พร้อมคิว
// คิวการดำเนินการสำหรับโหมดออฟไลน์
const operationQueue = [];
// จัดการข้อผิดพลาดด้านเครือข่าย
AuthErrorHandler.registerHandler('NETWORK_ERROR', async (error, context) => {
console.log('Network error - queueing operation');
// จัดคิวการดำเนินการ
if (context.operation) {
operationQueue.push({
operation: context.operation,
timestamp: Date.now(),
error: error
});
}
// แสดงโหมดออฟไลน์
showOfflineMode();
return { handled: true };
});
// ประมวลผลคิวเมื่อออนไลน์
window.addEventListener('online', async () => {
console.log('Back online - processing queue');
hideOfflineMode();
// ประมวลผลงานในคิว
while (operationQueue.length > 0) {
const item = operationQueue.shift();
try {
await item.operation.retry();
console.log('Operation completed:', item.operation.type);
} catch (error) {
console.error('Operation failed:', error);
// จัดคิวใหม่หากยังเป็นข้อผิดพลาดด้านเครือข่าย
if (error.type === 'NETWORK_ERROR') {
operationQueue.push(item);
break; // หยุดประมวลผล ยังออฟไลน์อยู่
}
}
}
// แสดงการแจ้งเตือน
if (operationQueue.length === 0) {
showNotification('All operations completed', 'success');
} else {
showNotification(`${operationQueue.length} operations pending`, 'warning');
}
});
// ล้างงานเก่าที่อยู่ในคิว (เก่ากว่า 1 ชั่วโมง)
setInterval(() => {
const now = Date.now();
const maxAge = 60 * 60 * 1000; // 1 ชั่วโมง
const filtered = operationQueue.filter(item => {
return (now - item.timestamp) < maxAge;
});
const removed = operationQueue.length - filtered.length;
if (removed > 0) {
console.log(`Removed ${removed} old operations from queue`);
operationQueue.length = 0;
operationQueue.push(...filtered);
}
}, 5 * 60 * 1000); // ตรวจสอบทุก 5 นาที5. Token Refresh พร้อม Fallback
// จัดการ token หมดอายุ
AuthErrorHandler.registerHandler('TOKEN_EXPIRED', async (error, context) => {
console.log('Token expired - attempting refresh');
try {
// พยายาม refresh token
await AuthManager.refreshToken();
console.log('Token refreshed successfully');
// ลองทำ operation เดิมอีกครั้ง
if (context.operation) {
return await context.operation.retry();
}
return { handled: true };
} catch (refreshError) {
console.error('Token refresh failed:', refreshError);
// เก็บ route ที่ต้องการ
const currentRoute = Router.getCurrentRoute();
if (currentRoute && !currentRoute.metadata?.public) {
AuthGuard.storeIntendedRoute(currentRoute, Router);
}
// เปลี่ยนเส้นทางไปหน้า login
await Router.navigate('/login');
// แสดงการแจ้งเตือน
showNotification(
'Your session has expired. Please login again.',
'warning'
);
return { handled: true };
}
});
// จัดการการ refresh ล้มเหลว
document.addEventListener('auth:error:token_refresh_failed', async () => {
// บังคับออกจากระบบ
await AuthManager.logout();
// ล้างข้อมูล auth ทั้งหมด
localStorage.clear();
sessionStorage.clear();
// เปลี่ยนเส้นทางไปหน้า login
await Router.navigate('/login');
});6. การติดตาม Error และ Analytics
// ติดตามข้อผิดพลาดทั้งหมดในระบบวิเคราะห์ (Analytics)
document.addEventListener('auth:error', async (e) => {
const { error, context } = e.detail;
// เตรียมข้อมูลข้อผิดพลาด
const errorData = {
type: error.type,
message: error.message,
context: {
operation: context.operation?.type,
route: Router.getCurrentRoute()?.path,
user: AuthManager.getUser()?.id
},
timestamp: new Date().toISOString(),
userAgent: navigator.userAgent,
url: window.location.href
};
// ส่งไปยัง analytics
try {
await fetch('/api/analytics/error', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(errorData)
});
} catch (err) {
console.error('Failed to log error:', err);
}
// บันทึกใน console ในโหมด development
if (isDevelopment) {
console.group('Auth Error');
console.error('Type:', error.type);
console.error('Message:', error.message);
console.error('Context:', context);
console.groupEnd();
}
});
// ติดตามการกู้คืนหลังเกิดข้อผิดพลาด
document.addEventListener('auth:error:recovered', (e) => {
const { error, recoveryMethod } = e.detail;
trackEvent('auth_error_recovered', {
error_type: error.type,
recovery_method: recoveryMethod
});
});
// ติดตามการลองใหม่
document.addEventListener('auth:error:retry', (e) => {
const { error, attempt, maxRetries } = e.detail;
trackEvent('auth_error_retry', {
error_type: error.type,
attempt: attempt,
max_retries: maxRetries
});
});เอกสารอ้างอิง API
เมธอด
handleError(error, context)
จัดการ error ด้วย actions ที่กำหนดไว้
พารามิเตอร์:
error(Object/Error) - Error objectcontext(Object) - Error context
คืนค่า: Promise<Object> - ผลลัพธ์การจัดการ
ตัวอย่าง:
try {
await AuthManager.login(credentials);
} catch (error) {
await AuthErrorHandler.handleError(error, {
operation: 'login',
route: Router.getCurrentRoute()
});
}registerHandler(errorType, handler)
ลงทะเบียน handler สำหรับ error ประเภทเฉพาะ
พารามิเตอร์:
errorType(string) - ประเภท Errorhandler(Function) - ฟังก์ชัน Handler
คืนค่า: void
ตัวอย่าง:
AuthErrorHandler.registerHandler('VALIDATION_ERROR', async (error) => {
displayValidationErrors(error.errors);
return { handled: true };
});setGlobalHandler(handler)
ตั้งค่าตัวจัดการข้อผิดพลาดระดับทั่วทั้งระบบ ซึ่งถูกเรียกสำหรับข้อผิดพลาดทุกประเภท
พารามิเตอร์:
handler(Function) - ฟังก์ชัน Global handler
คืนค่า: void
ตัวอย่าง:
AuthErrorHandler.setGlobalHandler(async (error, context) => {
await logErrorToService(error);
return { handled: false }; // ดำเนินการต่อไปยัง specific handlers
});setRetryDecider(decider)
ตั้งค่าตรรกะการตัดสินใจลองใหม่แบบกำหนดเอง
พารามิเตอร์:
decider(Function) - ฟังก์ชันตัดสินใจการลองใหม่
คืนค่า: void
ตัวอย่าง:
AuthErrorHandler.setRetryDecider(async (error, attempt) => {
if (error.type === 'NETWORK_ERROR' && attempt < 5) {
return { retry: true, delay: attempt * 1000 };
}
return { retry: false };
});registerRecovery(errorType, strategy)
ลงทะเบียนกลยุทธ์การกู้คืนสำหรับประเภท error
พารามิเตอร์:
errorType(string) - ประเภท Errorstrategy(Function) - ฟังก์ชันกลยุทธ์การกู้คืน
คืนค่า: void
ตัวอย่าง:
AuthErrorHandler.registerRecovery('TOKEN_EXPIRED', async (error, context) => {
await AuthManager.refreshToken();
return { recovered: true };
});clearHandlers()
ล้าง error handlers ที่ลงทะเบียนไว้ทั้งหมด
คืนค่า: void
ตัวอย่าง:
AuthErrorHandler.clearHandlers();แนวทางปฏิบัติที่ดี
1. จัดการข้อผิดพลาดอย่างสง่างาม
// ✅ ดี - จัดการข้อผิดพลาดอย่างสง่างาม
try {
await AuthManager.login(credentials);
} catch (error) {
// ข้อผิดพลาดถูกจัดการโดย AuthErrorHandler โดยอัตโนมัติ
console.log('Login failed, error handled');
}
// ❌ ไม่ดี - ไม่มีการจัดการข้อผิดพลาด
await AuthManager.login(credentials); // ข้อผิดพลาดไม่ถูกจัดการ!2. ให้ข้อมูลย้อนกลับแก่ผู้ใช้
// ✅ ดี - ให้ข้อมูลย้อนกลับที่ชัดเจนแก่ผู้ใช้
document.addEventListener('auth:error:validation', (e) => {
showValidationErrors(e.detail.errors);
showNotification('Please fix the errors and try again', 'error');
});
// ❌ ไม่ดี - ล้มเหลวแบบเงียบๆ
// ผู้ใช้ไม่รู้ว่าเกิดอะไรขึ้น3. บันทึกข้อผิดพลาดอย่างเหมาะสม
// ✅ ดี - บันทึกข้อผิดพลาดแบบมีโครงสร้าง
document.addEventListener('auth:error', (e) => {
const { error, context } = e.detail;
logger.error({
type: error.type,
message: error.message,
context: context,
timestamp: new Date().toISOString()
});
});
// ❌ ไม่ดี - ไม่มีการบันทึก
// ไม่สามารถ debug ปัญหาใน production ได้4. ใช้การลองใหม่ที่มีขีดจำกัด
// ✅ ดี - ลองใหม่ด้วยการหน่วงแบบยกกำลัง และมีขีดจำกัด
{
maxRetries: 3,
retryBackoff: 'exponential',
retryDelay: 1000
}
// ❌ ไม่ดี - ลองใหม่ไม่จำกัดจำนวนครั้ง
{
maxRetries: Infinity // ไม่มีวันหยุดพยายาม!
}ข้อผิดพลาดที่พบบ่อย
1. ไม่จัดการข้อผิดพลาดทุกประเภท
// ❌ ไม่ดี - จัดการเฉพาะกรณีไม่ได้รับอนุญาต
document.addEventListener('auth:error:unauthorized', () => {
redirectToLogin();
});
// แล้วข้อผิดพลาดด้านเครือข่ายล่ะ? แล้วข้อผิดพลาดจากการตรวจสอบข้อมูลล่ะ?
// ✅ ดี - จัดการข้อผิดพลาดทุกประเภท
document.addEventListener('auth:error', (e) => {
const { error } = e.detail;
switch (error.type) {
case 'UNAUTHORIZED':
redirectToLogin();
break;
case 'NETWORK_ERROR':
showOfflineMode();
break;
case 'VALIDATION_ERROR':
showValidationErrors(error.errors);
break;
default:
showGenericError(error.message);
}
});2. กลืนข้อผิดพลาด
// ❌ ไม่ดี - ข้อผิดพลาดหายไป
try {
await AuthManager.login(credentials);
} catch (error) {
// ไม่ทำอะไร - ข้อผิดพลาดสูญหาย
}
// ✅ ดี - อย่างน้อยก็บันทึกข้อผิดพลาด
try {
await AuthManager.login(credentials);
} catch (error) {
console.error('Login failed:', error);
// ส่งต่อให้ AuthErrorHandler จัดการต่อ
throw error;
}3. ลองใหม่โดยไม่มี Backoff
// ❌ ไม่ดี - ลองใหม่ทันทีตลอดเวลา
{
retryBackoff: 'fixed',
retryDelay: 0,
maxRetries: 999
}
// นี่มันทำลายเซิร์ฟเวอร์ชัดๆ!
// ✅ ดี - ใช้การหน่วงแบบยกกำลัง ที่มีขีดจำกัด
{
retryBackoff: 'exponential',
retryDelay: 1000,
maxRetries: 3
}เอกสารที่เกี่ยวข้อง
- Authentication.md - Authentication system overview
- AuthManager.md - Core authentication manager
- AuthGuard.md - Route protection
- TokenService.md - JWT token management
- AuthLoadingManager.md - Loading states