Now.js Framework Documentation
AuthLoadingManager - ระบบจัดการสถานะการโหลด
AuthLoadingManager - ระบบจัดการสถานะการโหลด
เอกสารสำหรับ AuthLoadingManager ซึ่งเป็นระบบจัดการสถานะการโหลด สำหรับการดำเนินการยืนยันตัวตน ของ Now.js Framework
📋 สารบัญ
- ภาพรวม
- การติดตั้งและนำเข้า (Installation & Import)
- การเริ่มต้นใช้งาน
- การดำเนินการโหลด
- ส่วนติดต่อผู้ใช้ระหว่างโหลด
- การติดตามความคืบหน้า
- เหตุการณ์การโหลด
- ตัวชี้วัดการโหลดกำหนดเอง
- การจัดการหมดเวลาโหลด
- ตัวอย่างการใช้งาน
- เอกสารอ้างอิง API
- แนวทางที่แนะนำ
- ข้อผิดพลาดที่พบบ่อย
ภาพรวม
AuthLoadingManager จัดการสถานะการโหลดสำหรับการดำเนินการยืนยันตัวตน เพื่อให้ feedback กับผู้ใช้ตลอดช่วงเวลาที่ระบบกำลังทำงาน
ฟีเจอร์หลัก
- ✅ การติดตามการทำงาน: ติดตามสถานะการโหลดของแต่ละปฏิบัติการ
- ✅ ส่วนติดต่อผู้ใช้ระหว่างโหลด: แสดงตัวบ่งชี้สถานะโหลดอัตโนมัติ
- ✅ การติดตามความคืบหน้า: ติดตามเปอร์เซ็นต์ความคืบหน้าของงาน
- ✅ ตัวชี้วัดหลายรูปแบบ: รองรับตัวบ่งชี้หลายประเภท เช่น สปินเนอร์ แถบความคืบหน้า หรือสเกเลตัน
- ✅ ข้อความกำหนดเอง: กำหนดข้อความระหว่างโหลดได้เอง
- ✅ การจัดการหมดเวลา: จัดการงานที่ใช้เวลานานเกินกำหนด
- ✅ รองรับโอเวอร์เลย์: แสดงโอเวอร์เลย์เต็มหน้าจอระหว่างโหลด
- ✅ รองรับการยกเลิก: ยกเลิกการทำงานที่กำลังโหลดอยู่ได้
- ✅ ระบบเหตุการณ์: ส่งเหตุการณ์เมื่อสถานะการโหลดเปลี่ยนแปลง
- ✅ ล้างสถานะอัตโนมัติ: เคลียร์สถานะการโหลดหลังจบงานอัตโนมัติ
เมื่อไหร่ควรใช้ AuthLoadingManager
✅ ใช้ AuthLoadingManager เมื่อ:
- ต้องการแสดง feedback ระหว่างขั้นตอนยืนยันตัวตน
- ต้องการติดตามความคืบหน้าของแต่ละปฏิบัติการ
- ต้องการประสบการณ์ UX ที่สม่ำเสมอระหว่างโหลด
- ต้องการรองรับกรณีหมดเวลาระหว่างโหลด
❌ ไม่ควรใช้เมื่อ:
- ปฏิบัติการจบเร็วมาก (น้อยกว่า 100ms)
- ไม่ต้องการแสดง UI ระหว่างโหลด
การติดตั้งและนำเข้า
AuthLoadingManager โหลดมาพร้อมกับ Now.js Framework และพร้อมใช้งานทันทีผ่าน window object:
// ไม่ต้อง import - พร้อมใช้งานทันที
console.log(window.AuthLoadingManager); // อ็อบเจ็กต์ AuthLoadingManager ที่ผูกไว้กับ windowการเริ่มต้นใช้งาน
การตั้งค่าพื้นฐาน
// AuthLoadingManager ทำงานร่วมกับ AuthManager อัตโนมัติ
// ไม่ต้องตั้งค่าแยกเพิ่มเติม
await AuthManager.init({
enabled: true,
// Loading configuration
loading: {
// แสดงตัวชี้วัดระหว่างโหลด
enabled: true,
// ระยะเวลาแสดงขั้นต่ำเพื่อป้องกันการกระพริบ
minDuration: 300,
// เวลาหมดในการโหลด (มิลลิวินาที)
timeout: 30000,
// ข้อความแสดงระหว่างโหลดค่าเริ่มต้น
defaultMessage: 'Loading...',
// ประเภทตัวชี้วัดการโหลด
indicator: 'spinner', // spinner, progress, skeleton, overlay
// แสดงโอเวอร์เลย์
overlay: true,
// ความทึบของโอเวอร์เลย์
overlayOpacity: 0.5
}
});
console.log('AuthLoadingManager initialized!');ลำดับการโหลด
เริ่มปฏิบัติการ
↓
┌─────────────────────┐
│ เริ่มแสดงสถานะโหลด │
│ - แสดงตัวชี้วัด │
│ - ส่งเหตุการณ์ │
└─────────┬───────────┘
↓
┌─────────────────────┐
│ ปฏิบัติการกำลังทำงาน │
│ - อัปเดตความคืบหน้า │
│ - แสดงข้อความ │
└─────────┬───────────┘
↓
┌─────────────────────┐
│ ตรวจสอบเวลาหมด │
│ - ยกเลิกหากจำเป็น │
│ - แสดงคำเตือน │
└─────────┬───────────┘
↓
┌─────────────────────┐
│ ปฏิบัติการเสร็จสิ้น │
│ - ซ่อนตัวชี้วัด │
│ - ส่งเหตุการณ์ │
│ - ทำความสะอาด │
└─────────────────────┘การดำเนินการโหลด
AuthLoadingManager ติดตามสถานะการโหลดสำหรับปฏิบัติการเหล่านี้:
1. การเข้าสู่ระบบ
// แสดงสถานะโหลดระหว่างเข้าสู่ระบบ
await AuthManager.login({
email: 'user@example.com',
password: 'password'
});
// ระบบจะแสดงและซ่อนสถานะโหลดให้อัตโนมัติข้อความระหว่างโหลด:
- เริ่มต้น: "Logging in..."
- สำเร็จ: "Login successful!"
- ล้มเหลว: "Login failed"
2. การออกจากระบบ
// แสดงสถานะโหลดระหว่างออกจากระบบ
await AuthManager.logout();ข้อความระหว่างโหลด:
- เริ่มต้น: "Logging out..."
- สำเร็จ: "Logged out"
3. การรีเฟรชโทเค็น
// แสดงสถานะโหลดระหว่างรีเฟรชโทเค็น
await AuthManager.refreshToken();ข้อความระหว่างโหลด:
- เริ่มต้น: "Refreshing session..."
- สำเร็จ: "Session refreshed"
- ล้มเหลว: "Session refresh failed"
4. การตรวจสอบเซสชัน
// แสดงสถานะโหลดระหว่างตรวจสอบเซสชัน
const isValid = await AuthManager.verifySession();ข้อความระหว่างโหลด:
- เริ่มต้น: "Verifying session..."
- สำเร็จ: "Session verified"
5. การเข้าสู่ระบบผ่านโซเชียล
// แสดงสถานะโหลดระหว่างใช้ OAuth
await AuthManager.loginWithGoogle();ข้อความระหว่างโหลด:
- เริ่มต้น: "Connecting to Google..."
- สำเร็จ: "Connected!"
6. การลงทะเบียน
// กำหนดปฏิบัติการเองพร้อมสถานะโหลด
await AuthLoadingManager.track('register', async () => {
const response = await fetch('/api/auth/register', {
method: 'POST',
body: JSON.stringify(userData)
});
return response.json();
});ข้อความระหว่างโหลด:
- เริ่มต้น: "Creating account..."
- สำเร็จ: "Account created!"
7. การรีเซ็ตรหัสผ่าน
await AuthLoadingManager.track('password_reset', async () => {
return await resetPassword(email);
}, {
message: 'Sending reset email...'
});8. การอัปเดตโปรไฟล์
await AuthLoadingManager.track('profile_update', async () => {
return await updateProfile(data);
}, {
message: 'Updating profile...',
showProgress: true
});9. การยืนยันอีเมล
await AuthLoadingManager.track('email_verify', async () => {
return await verifyEmail(code);
}, {
message: 'Verifying email...',
timeout: 10000
});ส่วนติดต่อผู้ใช้ระหว่างโหลด
1. ตัวชี้วัดแบบสปินเนอร์
// สปินเนอร์ค่าเริ่มต้น
await AuthManager.init({
loading: {
indicator: 'spinner',
message: 'Loading...'
}
});ตัวอย่าง HTML:
<div class="auth-loading">
<div class="spinner"></div>
<p>Loading...</p>
</div>2. แถบความคืบหน้า
// แถบความคืบหน้าพร้อมเปอร์เซ็นต์
await AuthManager.init({
loading: {
indicator: 'progress',
showPercentage: true
}
});
// อัปเดตเปอร์เซ็นต์ความคืบหน้า
AuthLoadingManager.updateProgress('login', 50); // 50%ตัวอย่าง HTML:
<div class="auth-loading">
<div class="progress-bar">
<div class="progress-fill" style="width: 50%"></div>
</div>
<p>50% Complete</p>
</div>3. โครง UI แบบสเกเลตัน
// โครง UI แบบสเกเลตันระหว่างโหลด
await AuthManager.init({
loading: {
indicator: 'skeleton',
skeletonType: 'profile' // profile, list, card
}
});ตัวอย่าง HTML:
<div class="auth-loading skeleton">
<div class="skeleton-avatar"></div>
<div class="skeleton-line"></div>
<div class="skeleton-line short"></div>
</div>4. โอเวอร์เลย์เต็มหน้าจอ
// โอเวอร์เลย์เต็มหน้าจอ
await AuthManager.init({
loading: {
indicator: 'overlay',
overlay: true,
overlayOpacity: 0.7,
overlayColor: '#000000'
}
});ตัวอย่าง HTML:
<div class="auth-loading-overlay" style="opacity: 0.7">
<div class="loading-content">
<div class="spinner"></div>
<p>Please wait...</p>
</div>
</div>5. ตัวชี้วัดกำหนดเอง
// ลงทะเบียนตัวชี้วัดกำหนดเอง
AuthLoadingManager.registerIndicator('custom', (options) => {
const container = document.createElement('div');
container.className = 'custom-loading';
container.innerHTML = `
<div class="custom-spinner"></div>
<h3>${options.title || 'Loading'}</h3>
<p>${options.message || 'Please wait...'}</p>
`;
return container;
});
// ใช้งานตัวชี้วัดกำหนดเอง
await AuthManager.init({
loading: {
indicator: 'custom',
title: 'Authenticating',
message: 'Verifying your credentials...'
}
});การติดตามความคืบหน้า
1. อัปเดตความคืบหน้าแบบกำหนดเอง
// ติดตามปฏิบัติการพร้อมความคืบหน้า
const operationId = AuthLoadingManager.start('upload', {
message: 'Uploading file...',
showProgress: true
});
// อัปเดตเปอร์เซ็นต์ความคืบหน้า
AuthLoadingManager.updateProgress(operationId, 25); // 25%
AuthLoadingManager.updateProgress(operationId, 50); // 50%
AuthLoadingManager.updateProgress(operationId, 75); // 75%
AuthLoadingManager.updateProgress(operationId, 100); // 100%
// จบสถานะโหลด
AuthLoadingManager.end(operationId);2. ความคืบหน้าอัตโนมัติ
// ติดตามความคืบหน้าการอัปโหลดอัตโนมัติ
await AuthLoadingManager.trackWithProgress('upload', () => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.upload.addEventListener('progress', (e) => {
if (e.lengthComputable) {
const percent = (e.loaded / e.total) * 100;
AuthLoadingManager.updateProgress('upload', percent);
}
});
xhr.addEventListener('load', () => resolve(xhr.response));
xhr.addEventListener('error', reject);
xhr.open('POST', '/api/upload');
xhr.send(formData);
});
}, {
message: 'Uploading...',
showProgress: true
});3. ความคืบหน้าตามขั้นตอน
// ปฏิบัติการหลายขั้นตอน
const steps = [
{ name: 'validate', message: 'Validating data...' },
{ name: 'process', message: 'Processing...' },
{ name: 'save', message: 'Saving...' },
{ name: 'notify', message: 'Sending notifications...' }
];
const operationId = AuthLoadingManager.start('multi_step', {
steps: steps,
currentStep: 0
});
for (let i = 0; i < steps.length; i++) {
const step = steps[i];
// อัปเดตเป็นขั้นตอนปัจจุบัน
AuthLoadingManager.updateStep(operationId, i, step.message);
// ดำเนินงานในแต่ละขั้น
await performStep(step.name);
// อัปเดตเปอร์เซ็นต์ความคืบหน้า
const progress = ((i + 1) / steps.length) * 100;
AuthLoadingManager.updateProgress(operationId, progress);
}
AuthLoadingManager.end(operationId);4. ความคืบหน้าแบบไม่ทราบระยะเวลา
// ปฏิบัติการที่ไม่ทราบระยะเวลา
const operationId = AuthLoadingManager.start('processing', {
message: 'Processing...',
indeterminate: true // แสดงสปินเนอร์แทนแถบความคืบหน้า
});
await performLongOperation();
AuthLoadingManager.end(operationId);เหตุการณ์การโหลด
เหตุการณ์ที่มีให้ใช้งาน
// ฟังเหตุการณ์เกี่ยวกับสถานะการโหลด
// 1. เริ่มโหลด
document.addEventListener('auth:loading:start', (e) => {
const { operation, options } = e.detail;
console.log(`Loading started: ${operation}`);
});
// 2. จบการโหลด
document.addEventListener('auth:loading:end', (e) => {
const { operation, duration } = e.detail;
console.log(`Loading ended: ${operation} (${duration}ms)`);
});
// 3. ความคืบหน้าเปลี่ยน
document.addEventListener('auth:loading:progress', (e) => {
const { operation, progress } = e.detail;
console.log(`Progress: ${progress}%`);
});
// 4. หมดเวลาระหว่างโหลด
document.addEventListener('auth:loading:timeout', (e) => {
const { operation, timeout } = e.detail;
console.log(`Operation timed out: ${operation}`);
});
// 5. ยกเลิกการโหลด
document.addEventListener('auth:loading:cancelled', (e) => {
const { operation, reason } = e.detail;
console.log(`Loading cancelled: ${operation}`);
});
// 6. เปลี่ยนขั้นตอน
document.addEventListener('auth:loading:step', (e) => {
const { operation, step, total } = e.detail;
console.log(`Step ${step}/${total}`);
});ตัวชี้วัดการโหลดกำหนดเอง
1. สปินเนอร์กำหนดเอง
// ลงทะเบียนสปินเนอร์กำหนดเอง
AuthLoadingManager.registerIndicator('custom-spinner', (options) => {
const div = document.createElement('div');
div.className = 'custom-loading-spinner';
div.innerHTML = `
<style>
.custom-loading-spinner {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 2rem;
}
.spinner-circle {
width: 50px;
height: 50px;
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.spinner-text {
margin-top: 1rem;
color: #666;
font-size: 14px;
}
</style>
<div class="spinner-circle"></div>
<div class="spinner-text">${options.message || 'Loading...'}</div>
`;
return div;
});
// ใช้งานสปินเนอร์กำหนดเอง
await AuthManager.init({
loading: {
indicator: 'custom-spinner'
}
});2. แถบความคืบหน้าแบบแอนิเมชัน
AuthLoadingManager.registerIndicator('animated-progress', (options) => {
const div = document.createElement('div');
div.className = 'animated-progress-bar';
div.innerHTML = `
<style>
.animated-progress-bar {
width: 100%;
max-width: 400px;
margin: 2rem auto;
}
.progress-container {
width: 100%;
height: 8px;
background: #e0e0e0;
border-radius: 4px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #4CAF50, #45a049);
transition: width 0.3s ease;
position: relative;
}
.progress-fill::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(
90deg,
transparent,
rgba(255,255,255,0.3),
transparent
);
animation: shimmer 1.5s infinite;
}
@keyframes shimmer {
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
}
.progress-text {
text-align: center;
margin-top: 0.5rem;
font-size: 14px;
color: #666;
}
</style>
<div class="progress-container">
<div class="progress-fill" style="width: 0%"></div>
</div>
<div class="progress-text">${options.message || 'Loading...'}</div>
`;
// เมธอดสำหรับอัปเดตความคืบหน้า
div.updateProgress = (percent) => {
const fill = div.querySelector('.progress-fill');
const text = div.querySelector('.progress-text');
fill.style.width = `${percent}%`;
text.textContent = `${Math.round(percent)}% ${options.message || ''}`;
};
return div;
});3. หน้าจอสเกเลตัน
AuthLoadingManager.registerIndicator('profile-skeleton', (options) => {
const div = document.createElement('div');
div.className = 'profile-skeleton';
div.innerHTML = `
<style>
.profile-skeleton {
padding: 2rem;
max-width: 600px;
margin: 0 auto;
}
.skeleton-item {
background: linear-gradient(
90deg,
#f0f0f0 25%,
#e0e0e0 50%,
#f0f0f0 75%
);
background-size: 200% 100%;
animation: loading 1.5s infinite;
border-radius: 4px;
margin-bottom: 1rem;
}
@keyframes loading {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
.skeleton-avatar {
width: 80px;
height: 80px;
border-radius: 50%;
margin-bottom: 1rem;
}
.skeleton-line {
height: 16px;
margin-bottom: 0.5rem;
}
.skeleton-line.short {
width: 60%;
}
</style>
<div class="skeleton-avatar skeleton-item"></div>
<div class="skeleton-line skeleton-item"></div>
<div class="skeleton-line short skeleton-item"></div>
<div class="skeleton-line skeleton-item"></div>
`;
return div;
});การจัดการหมดเวลาโหลด
1. ตั้งค่าเวลาหมดสากล
// ตั้งค่าเวลาหมดระดับระบบ
await AuthManager.init({
loading: {
timeout: 30000, // 30 seconds
timeoutAction: 'cancel' // cancel, notify, or ignore
}
});2. ตั้งค่าเวลาหมดรายปฏิบัติการ
// ตั้งเวลาหมดต่างกันในแต่ละปฏิบัติการ
await AuthLoadingManager.track('slow_operation', async () => {
return await slowApiCall();
}, {
timeout: 60000, // 60 วินาทีสำหรับปฏิบัติการนี้
timeoutAction: 'notify'
});3. จัดการเมื่อหมดเวลา
// ฟังเหตุการณ์เวลาหมด
document.addEventListener('auth:loading:timeout', async (e) => {
const { operation, timeout } = e.detail;
console.log(`Operation ${operation} timed out after ${timeout}ms`);
// ถามผู้ใช้ว่าจะรอต่อหรือยกเลิก
const result = await showTimeoutDialog({
message: 'Operation is taking longer than expected.',
options: ['Continue Waiting', 'Cancel']
});
if (result === 'Cancel') {
AuthLoadingManager.cancel(operation);
} else {
// ขยายเวลาให้ยาวขึ้น
AuthLoadingManager.extendTimeout(operation, 30000);
}
});4. ยกเลิกปฏิบัติการ
// เริ่มปฏิบัติการที่ยกเลิกได้
const operationId = AuthLoadingManager.start('long_task', {
message: 'Processing...',
cancellable: true
});
// แสดงปุ่มยกเลิก
showCancelButton(() => {
AuthLoadingManager.cancel(operationId);
});
// ดำเนินงานที่อาจใช้เวลานาน
try {
await performLongTask();
AuthLoadingManager.end(operationId);
} catch (error) {
if (error.cancelled) {
console.log('Operation cancelled by user');
} else {
throw error;
}
}ตัวอย่างการใช้งาน
1. การเข้าสู่ระบบพร้อม UI โหลดกำหนดเอง
// สร้าง UI ระหว่างโหลดสำหรับการเข้าสู่ระบบ
async function handleLogin(e) {
e.preventDefault();
const email = emailInput.value;
const password = passwordInput.value;
// เริ่มสถานะโหลดกำหนดเอง
const loadingId = AuthLoadingManager.start('login', {
message: 'Signing you in...',
indicator: 'custom-spinner',
overlay: true
});
try {
// เรียกการเข้าสู่ระบบ
await AuthManager.login({ email, password });
// อัปเดตข้อความเมื่อสำเร็จ
AuthLoadingManager.updateMessage(loadingId, 'Success! Redirecting...');
// หน่วงเวลาเล็กน้อยเพื่อแสดงข้อความสำเร็จ
await sleep(1000);
// จบสถานะโหลด
AuthLoadingManager.end(loadingId);
// เปลี่ยนเส้นทางไปยังหน้าถัดไป
await Router.navigate('/dashboard');
} catch (error) {
// จบสถานะโหลด
AuthLoadingManager.end(loadingId);
// แสดงข้อผิดพลาด
showNotification(error.message, 'error');
}
}
// ฟังก์ชันช่วยหน่วงเวลา
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}2. การลงทะเบียนหลายขั้นตอน
async function handleRegistration(userData) {
const steps = [
{ name: 'validate', message: 'Validating information...' },
{ name: 'create', message: 'Creating account...' },
{ name: 'verify', message: 'Verifying email...' },
{ name: 'complete', message: 'Finalizing setup...' }
];
const loadingId = AuthLoadingManager.start('register', {
steps: steps,
indicator: 'animated-progress',
overlay: true
});
try {
// ขั้นที่ 1: ตรวจสอบข้อมูล
AuthLoadingManager.updateStep(loadingId, 0, steps[0].message);
await validateUserData(userData);
AuthLoadingManager.updateProgress(loadingId, 25);
// ขั้นที่ 2: สร้างบัญชี
AuthLoadingManager.updateStep(loadingId, 1, steps[1].message);
const user = await createAccount(userData);
AuthLoadingManager.updateProgress(loadingId, 50);
// ขั้นที่ 3: ส่งอีเมลยืนยัน
AuthLoadingManager.updateStep(loadingId, 2, steps[2].message);
await sendVerificationEmail(user.email);
AuthLoadingManager.updateProgress(loadingId, 75);
// ขั้นที่ 4: ตั้งค่าขั้นสุดท้าย
AuthLoadingManager.updateStep(loadingId, 3, steps[3].message);
await completeUserSetup(user);
AuthLoadingManager.updateProgress(loadingId, 100);
// ข้อความเมื่อสำเร็จ
AuthLoadingManager.updateMessage(loadingId, 'Registration complete!');
await sleep(1500);
AuthLoadingManager.end(loadingId);
// ไปยังหน้าแดชบอร์ด
await Router.navigate('/dashboard');
} catch (error) {
AuthLoadingManager.end(loadingId);
showNotification(error.message, 'error');
}
}3. อัปโหลดไฟล์พร้อมความคืบหน้า
async function uploadProfilePicture(file) {
const loadingId = AuthLoadingManager.start('upload', {
message: 'Uploading profile picture...',
showProgress: true,
cancellable: true
});
// แสดงปุ่มยกเลิก
const cancelBtn = showCancelButton();
cancelBtn.onclick = () => {
AuthLoadingManager.cancel(loadingId);
};
try {
// สร้าง FormData สำหรับอัปโหลด
const formData = new FormData();
formData.append('file', file);
// อัปโหลดพร้อมติดตามความคืบหน้า
const response = await new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
// ติดตามเปอร์เซ็นต์การอัปโหลด
xhr.upload.addEventListener('progress', (e) => {
if (e.lengthComputable) {
const percent = (e.loaded / e.total) * 100;
AuthLoadingManager.updateProgress(loadingId, percent);
}
});
// จัดการเมื่ออัปโหลดเสร็จ
xhr.addEventListener('load', () => {
if (xhr.status === 200) {
resolve(JSON.parse(xhr.response));
} else {
reject(new Error('Upload failed'));
}
});
xhr.addEventListener('error', () => reject(new Error('Network error')));
xhr.addEventListener('abort', () => reject(new Error('Upload cancelled')));
// ส่งคำขออัปโหลด
xhr.open('POST', '/api/profile/picture');
xhr.send(formData);
// เก็บ xhr ไว้ใช้ยกเลิก
AuthLoadingManager.setContext(loadingId, { xhr });
});
// เมื่อสำเร็จ
AuthLoadingManager.updateMessage(loadingId, 'Upload complete!');
await sleep(1000);
AuthLoadingManager.end(loadingId);
// ปรับปรุง UI ให้สะท้อนผลลัพธ์
updateProfilePicture(response.url);
showNotification('Profile picture updated!', 'success');
} catch (error) {
AuthLoadingManager.end(loadingId);
if (error.message === 'Upload cancelled') {
showNotification('Upload cancelled', 'info');
} else {
showNotification(error.message, 'error');
}
} finally {
hideCancelButton();
}
}
// จัดการเมื่อยกเลิกการอัปโหลด
document.addEventListener('auth:loading:cancel', (e) => {
const { operation, context } = e.detail;
if (operation === 'upload' && context.xhr) {
context.xhr.abort();
}
});4. ตรวจสอบเซสชันพร้อมหน้าสเกเลตัน
async function initializeApp() {
// แสดงสเกเลตันระหว่างตรวจสอบเซสชัน
const loadingId = AuthLoadingManager.start('init', {
indicator: 'profile-skeleton',
message: 'Loading your profile...'
});
try {
// ตรวจสอบสถานะเซสชัน
const isAuthenticated = await AuthManager.verifySession();
if (isAuthenticated) {
// โหลดข้อมูลผู้ใช้
const user = await loadUserData();
// โหลดการตั้งค่าผู้ใช้
const preferences = await loadUserPreferences();
// เริ่มต้นแอปด้วยข้อมูลผู้ใช้
await initializeAppWithUser(user, preferences);
// จบสถานะโหลด
AuthLoadingManager.end(loadingId);
// แสดงส่วนหลักของแอป
showMainApp();
} else {
// ยังไม่ได้ยืนยันตัวตน
AuthLoadingManager.end(loadingId);
// ไปยังหน้าล็อกอิน
await Router.navigate('/login');
}
} catch (error) {
AuthLoadingManager.end(loadingId);
showErrorScreen(error);
}
}
// เรียกฟังก์ชันเมื่อหน้าโหลดเสร็จ
initializeApp();5. ปฏิบัติการแบบกลุ่มพร้อมความคืบหน้า
async function processUserData(users) {
const total = users.length;
const loadingId = AuthLoadingManager.start('batch_process', {
message: `Processing ${total} users...`,
showProgress: true,
indeterminate: false
});
try {
let processed = 0;
let failed = 0;
for (const user of users) {
try {
// ประมวลผลข้อมูลผู้ใช้แต่ละคน
await processUser(user);
processed++;
} catch (error) {
console.error(`Failed to process user ${user.id}:`, error);
failed++;
}
// อัปเดตเปอร์เซ็นต์ความคืบหน้า
const progress = (processed / total) * 100;
AuthLoadingManager.updateProgress(loadingId, progress);
// ปรับข้อความให้แสดงสถานะล่าสุด
AuthLoadingManager.updateMessage(
loadingId,
`Processing: ${processed}/${total} (${failed} failed)`
);
}
// เมื่อดำเนินการครบ
AuthLoadingManager.updateProgress(loadingId, 100);
AuthLoadingManager.updateMessage(
loadingId,
`Complete! Processed ${processed}, Failed ${failed}`
);
await sleep(2000);
AuthLoadingManager.end(loadingId);
// แสดงสรุปผล
showBatchSummary({ processed, failed });
} catch (error) {
AuthLoadingManager.end(loadingId);
showNotification('Batch processing failed', 'error');
}
}6. การหมดเวลาที่ให้ผู้ใช้ตัดสินใจ
async function performLongOperation() {
const loadingId = AuthLoadingManager.start('long_op', {
message: 'This may take a while...',
timeout: 15000, // 15 วินาที
cancellable: true
});
try {
// เริ่มปฏิบัติการ
const operation = startLongOperation();
// เก็บไว้สำหรับยกเลิก
AuthLoadingManager.setContext(loadingId, { operation });
// รอผลลัพธ์
const result = await operation;
AuthLoadingManager.end(loadingId);
return result;
} catch (error) {
AuthLoadingManager.end(loadingId);
throw error;
}
}
// จัดการเมื่อหมดเวลา
document.addEventListener('auth:loading:timeout', async (e) => {
const { operation } = e.detail;
if (operation === 'long_op') {
// แสดงไดอะล็อกให้เลือก
const choice = await showDialog({
title: 'Taking Longer Than Expected',
message: 'The operation is taking longer than usual. What would you like to do?',
buttons: [
{ label: 'Keep Waiting', value: 'wait' },
{ label: 'Cancel', value: 'cancel', variant: 'secondary' }
]
});
if (choice === 'cancel') {
AuthLoadingManager.cancel(operation);
} else {
// ขยายเวลาต่ออีก 15 วินาที
AuthLoadingManager.extendTimeout(operation, 15000);
}
}
});
// จัดการเมื่อถูกยกเลิก
document.addEventListener('auth:loading:cancelled', (e) => {
const { operation, context } = e.detail;
if (operation === 'long_op' && context.operation) {
context.operation.cancel();
}
});เอกสารอ้างอิง API
เมธอด
start(operation, options)
เริ่มติดตามสถานะการโหลดสำหรับปฏิบัติการหนึ่ง
พารามิเตอร์:
operation(string) - รหัสระบุปฏิบัติการoptions(Object) - ตัวเลือกการตั้งค่าการโหลด
ค่าที่ส่งกลับ: string - ไอดีของปฏิบัติการ
ตัวอย่าง:
const id = AuthLoadingManager.start('login', {
message: 'Logging in...',
indicator: 'spinner',
overlay: true
});end(operationId)
ยุติสถานะการโหลดของปฏิบัติการ
พารามิเตอร์:
operationId(string) - ไอดีของปฏิบัติการ
ค่าที่ส่งกลับ: void
ตัวอย่าง:
AuthLoadingManager.end(operationId);track(operation, fn, options)
ติดตามฟังก์ชันแบบอะซิงก์พร้อมสถานะการโหลด
พารามิเตอร์:
operation(string) - รหัสปฏิบัติการfn(Function) - ฟังก์ชันอะซิงก์ที่ต้องการติดตามoptions(Object) - ตัวเลือกการแสดงผล
ค่าที่ส่งกลับ: Promise<any> - ผลลัพธ์ของฟังก์ชัน
ตัวอย่าง:
const result = await AuthLoadingManager.track('api_call', async () => {
return await fetch('/api/data').then(r => r.json());
}, {
message: 'Loading data...'
});updateProgress(operationId, percent)
อัปเดตเปอร์เซ็นต์ความคืบหน้า
พารามิเตอร์:
operationId(string) - ไอดีของปฏิบัติการpercent(number) - ค่าเปอร์เซ็นต์ (0-100)
ค่าที่ส่งกลับ: void
ตัวอย่าง:
AuthLoadingManager.updateProgress(operationId, 50);updateMessage(operationId, message)
ปรับข้อความระหว่างโหลด
พารามิเตอร์:
operationId(string) - ไอดีของปฏิบัติการmessage(string) - ข้อความใหม่
ค่าที่ส่งกลับ: void
ตัวอย่าง:
AuthLoadingManager.updateMessage(operationId, 'Almost done...');updateStep(operationId, step, message)
อัปเดตขั้นตอนปัจจุบันของปฏิบัติการหลายขั้น
พารามิเตอร์:
operationId(string) - ไอดีของปฏิบัติการstep(number) - ลำดับขั้นตอนปัจจุบันmessage(string) - ข้อความสำหรับขั้นตอนนั้น
ค่าที่ส่งกลับ: void
ตัวอย่าง:
AuthLoadingManager.updateStep(operationId, 2, 'Processing data...');cancel(operationId)
ยกเลิกปฏิบัติการ
พารามิเตอร์:
operationId(string) - ไอดีของปฏิบัติการ
ค่าที่ส่งกลับ: void
ตัวอย่าง:
AuthLoadingManager.cancel(operationId);extendTimeout(operationId, duration)
ขยายเวลาหมดสำหรับปฏิบัติการ
พารามิเตอร์:
operationId(string) - ไอดีของปฏิบัติการduration(number) - เวลาที่เพิ่มขึ้น (มิลลิวินาที)
ค่าที่ส่งกลับ: void
ตัวอย่าง:
AuthLoadingManager.extendTimeout(operationId, 30000); // +30sregisterIndicator(name, factory)
ลงทะเบียนตัวชี้วัดการโหลดกำหนดเอง
พารามิเตอร์:
name(string) - ชื่อตัวชี้วัดfactory(Function) - ฟังก์ชันสร้าง DOM สำหรับตัวชี้วัด
ค่าที่ส่งกลับ: void
ตัวอย่าง:
AuthLoadingManager.registerIndicator('custom', (options) => {
const div = document.createElement('div');
div.innerHTML = '<div class="custom-loader"></div>';
return div;
});isLoading(operation)
ตรวจสอบว่าปฏิบัติการกำลังโหลดอยู่หรือไม่
พารามิเตอร์:
operation(string) - รหัสปฏิบัติการ
ค่าที่ส่งกลับ: boolean
ตัวอย่าง:
if (AuthLoadingManager.isLoading('login')) {
console.log('Login in progress');
}getLoadingState(operation)
ดึงสถานะการโหลดของปฏิบัติการ
พารามิเตอร์:
operation(string) - รหัสปฏิบัติการ
ค่าที่ส่งกลับ: Object|null - ข้อมูลสถานะการโหลดหรือ null
ตัวอย่าง:
const state = AuthLoadingManager.getLoadingState('login');
console.log(state.progress); // Current progresssetContext(operationId, context)
จัดเก็บข้อมูลบริบทของปฏิบัติการ
พารามิเตอร์:
operationId(string) - ไอดีของปฏิบัติการcontext(Object) - ข้อมูลบริบท
ค่าที่ส่งกลับ: void
ตัวอย่าง:
AuthLoadingManager.setContext(operationId, { xhr, controller });แนวทางที่แนะนำ
1. กำหนดเวลาขั้นต่ำเพื่อกันการกระพริบ
// ✅ ดี - ป้องกันการกระพริบของสถานะโหลด
await AuthManager.init({
loading: {
minDuration: 300 // แสดงอย่างน้อย 300 มิลลิวินาที
}
});
// ❌ ไม่ดี - โหลดกระพริบเมื่อดำเนินการเร็วเกินไป
{
minDuration: 0
}2. เลือกตัวชี้วัดให้เหมาะกับงาน
// ✅ ดี - เลือกตัวชี้วัดเหมาะสม
const indicators = {
'login': 'spinner', // งานสั้น
'upload': 'progress', // ติดตามความคืบหน้าได้
'init': 'skeleton', // โหลดโครง UI
'batch': 'progress' // งานกลุ่มที่มีความคืบหน้า
};
// ❌ ไม่ดี - เลือกตัวชี้วัดไม่เหมาะ
{
'upload': 'skeleton' // ควรแสดงความคืบหน้า!
}3. ใช้ข้อความที่ชัดเจน
// ✅ ดี - ข้อความชัดเจน
{
message: 'Uploading profile picture...'
}
// ❌ ไม่ดี - ข้อความกว้างเกินไป
{
message: 'Loading...' // โหลดอะไรอยู่?
}4. จัดการเวลาหมดอย่างนุ่มนวล
// ✅ ดี - จัดการเวลาหมด
document.addEventListener('auth:loading:timeout', async (e) => {
const choice = await askUser('Continue or Cancel?');
if (choice === 'continue') {
AuthLoadingManager.extendTimeout(e.detail.operation, 30000);
} else {
AuthLoadingManager.cancel(e.detail.operation);
}
});
// ❌ ไม่ดี - เพิกเฉยเวลาหมด
// ปฏิบัติการจะค้างอยู่ข้อผิดพลาดที่พบบ่อย
1. ลืมจบสถานะการโหลด
// ❌ ไม่ดี - สถานะโหลดไม่ยอมจบ
const id = AuthLoadingManager.start('operation');
await doWork();
// ลืมเรียก AuthLoadingManager.end(id)!
// ✅ ดี - จบสถานะโหลดทุกครั้ง
const id = AuthLoadingManager.start('operation');
try {
await doWork();
} finally {
AuthLoadingManager.end(id); // จบสถานะเสมอ
}2. ลืมจัดการข้อผิดพลาด
// ❌ ไม่ดี - โหลดค้างเมื่อเกิดข้อผิดพลาด
const id = AuthLoadingManager.start('login');
await AuthManager.login(credentials); // อาจโยนข้อผิดพลาด
AuthLoadingManager.end(id);
// ✅ ดี - จบสถานะโหลดแม้เกิดข้อผิดพลาด
const id = AuthLoadingManager.start('login');
try {
await AuthManager.login(credentials);
} catch (error) {
// จัดการข้อผิดพลาด
} finally {
AuthLoadingManager.end(id);
}3. มีสถานะโหลดซ้อนกันมากเกินไป
// ❌ ไม่ดี - มีโอเวอร์เลย์หลายชั้น
for (const item of items) {
const id = AuthLoadingManager.start('process', { overlay: true });
await process(item);
AuthLoadingManager.end(id);
}
// โอเวอร์เลย์ซ้อนกันเต็มหน้าจอ!
// ✅ ดี - ใช้สถานะโหลดเดียวสำหรับงานชุด
const id = AuthLoadingManager.start('batch', { overlay: true });
for (let i = 0; i < items.length; i++) {
await process(items[i]);
AuthLoadingManager.updateProgress(id, (i / items.length) * 100);
}
AuthLoadingManager.end(id);เอกสารเกี่ยวข้อง
- Authentication.md - ภาพรวมระบบยืนยันตัวตน
- AuthManager.md - ผู้จัดการการยืนยันตัวตนหลัก
- AuthGuard.md - การป้องกันเส้นทาง
- AuthErrorHandler.md - การจัดการข้อผิดพลาด
- TokenService.md - การจัดการโทเค็น JWT