Now.js Framework Documentation
EventManager - Custom Event System
EventManager - Custom Event System
เอกสารสำหรับ EventManager ซึ่งเป็น Custom Event System สำหรับการจัดการ Events ในระดับ Application
📋 สารบัญ
- ภาพรวม
- การติดตั้งและนำเข้า
- การใช้งานพื้นฐาน
- API หลัก
- Patterns และ Wildcards
- Event Groups
- Priority System
- Middleware
- Async Events
- การจัดการ Context
- Cleanup และ Memory Management
- Integration กับ Now.js
- ตัวอย่างการใช้งาน
- API Reference
- Best Practices
ภาพรวม
EventManager เป็น Custom Event System ที่ออกแบบมาสำหรับจัดการ Events ในระดับ Application (ไม่ใช่ DOM Events) มีความสามารถขั้นสูงเหนือกว่า EventEmitter แบบปกติ
คุณสมบัติหลัก
- ✅ Event Bus Pattern: ระบบ Publish/Subscribe แบบกลาง
- ✅ Pattern Matching: รองรับ Wildcards และ Regular Expression
- ✅ Event Hierarchy: รองรับ Namespaced Events (dot notation)
- ✅ Event Groups: จัดกลุ่ม Events เพื่อจัดการร่วมกัน
- ✅ Priority System: กำหนดลำดับการทำงานของ Listeners
- ✅ Middleware: Intercept และแก้ไข Events ก่อนส่งต่อ
- ✅ Async Support: รองรับ Async/Await และ Promises
- ✅ Context Control: ควบคุม Event Propagation
- ✅ Auto Cleanup: จัดการหน่วยความจำอัตโนมัติ
- ✅ Debug Mode: ติดตาม Event History และข้อมูล Debug
ความแตกต่างจาก EventSystemManager
| คุณสมบัติ | EventManager | EventSystemManager |
|---|---|---|
| จุดประสงค์ | Application Events | DOM Events |
| API หลัก | on(), emit(), off() |
addEventListener() |
| Event Types | Custom events | DOM events (click, submit, etc.) |
| Target | Application logic | DOM elements |
| Patterns | ✅ รองรับ wildcards | ❌ ไม่รองรับ |
| Middleware | ✅ รองรับ | ❌ ไม่รองรับ |
| Groups | ✅ รองรับ | ❌ ไม่รองรับ |
| Priority | ✅ รองรับ | ✅ รองรับ |
เมื่อใดควรใช้ EventManager
✅ ใช้ EventManager เมื่อ:
- ต้องการ Event Bus สำหรับ Application
- ต้องการสื่อสารระหว่าง Components
- ต้องการ Decouple ระหว่าง Modules
- ต้องการ Pattern Matching (wildcards)
- ต้องการ Event Groups
- ต้องการ Middleware
❌ อย่าใช้ EventManager เมื่อ:
- ต้องการจัดการ DOM Events (ใช้ EventSystemManager)
- ต้องการ Event Delegation
- ต้องการ Native DOM Event Features
การติดตั้งและนำเข้า
EventManager โหลดพร้อมกับ Now.js Framework และพร้อมใช้งานทันทีผ่าน window object:
// ไม่ต้อง import - พร้อมใช้งานทันที
console.log(window.EventManager); // EventManager object
// หรือเข้าถึงผ่าน Now.js
const eventManager = Now.getManager('event');Dependencies
EventManager ต้องการ dependencies เหล่านี้:
- ErrorManager - สำหรับจัดการข้อผิดพลาด
- Utils - สำหรับ UUID generation (optional)
การเริ่มต้นใช้งาน
// เริ่มต้นด้วย default config
await EventManager.init();
// หรือเริ่มต้นพร้อม custom config
await EventManager.init({
asyncTimeout: 10000,
maxListeners: 1000,
traceEvents: true,
cleanup: {
enabled: true,
interval: 60000,
maxAge: 1800000
}
});การใช้งานพื้นฐาน
1. ลงทะเบียน Listener (on)
// พื้นฐาน
EventManager.on('user:login', (context) => {
console.log('User logged in:', context.data);
});
// พร้อม options
EventManager.on('user:login', (context) => {
console.log('User logged in:', context.data.userId);
}, {
priority: 10,
once: false
});2. ส่ง Event (emit)
// ส่ง event พร้อม data
await EventManager.emit('user:login', {
userId: 123,
username: 'john_doe',
timestamp: Date.now()
});
// รับค่า results จาก listeners
const results = await EventManager.emit('calculate:sum', {
numbers: [1, 2, 3, 4, 5]
});
console.log('Results:', results); // [15]3. ยกเลิก Listener (off)
// ยกเลิกทั้งหมดของ event
EventManager.off('user:login');
// ยกเลิกเฉพาะ listener ที่ระบุ
const unsubscribe = EventManager.on('user:login', handler);
unsubscribe(); // ยกเลิก listener นี้
// หรือใช้ listener ID
EventManager.off('user:login', listenerId);4. Listen ครั้งเดียว (once)
// ทำงานครั้งเดียวแล้วถูกลบ
EventManager.once('app:initialized', (context) => {
console.log('App initialized!');
});
// หรือใช้ option once
EventManager.on('popup:shown', handler, {once: true});API หลัก
on(event, callback, options?)
ลงทะเบียน listener สำหรับ event
const unsubscribe = EventManager.on(eventName, callback, options);Parameters:
event(string) - ชื่อ event หรือ patterncallback(function) - ฟังก์ชันที่จะถูกเรียก รับ context objectoptions(object) - optionalpriority(number) - ลำดับความสำคัญ (เลขมากทำก่อน) default: 0once(boolean) - ทำงานครั้งเดียว default: falseasync(boolean) - รองรับ async handler default: falsegroup(string) - จัดอยู่ใน grouptimeout(number) - async timeout (ms) default: 5000maxRetries(number) - จำนวนครั้งที่ retry เมื่อเกิดข้อผิดพลาด default: 1onError(function) - error handler
Returns: Function สำหรับยกเลิก listener
ตัวอย่าง:
// พื้นฐาน
EventManager.on('message:received', (context) => {
console.log('Message:', context.data.text);
});
// พร้อม priority
EventManager.on('data:processing', handleData, {
priority: 100 // ทำก่อน listeners อื่น
});
// พร้อม group
EventManager.on('notification:*', handleNotification, {
group: 'notifications'
});
// async handler
EventManager.on('user:register', async (context) => {
await sendWelcomeEmail(context.data.email);
return {success: true};
}, {
async: true,
timeout: 10000
});emit(event, data?)
ส่ง event พร้อม data
const results = await EventManager.emit(eventName, data);Parameters:
event(string) - ชื่อ eventdata(object) - ข้อมูลที่ต้องการส่ง optional
Returns: Promise - array ของ results จาก listeners
ตัวอย่าง:
// ส่ง event
await EventManager.emit('cart:item-added', {
productId: 123,
quantity: 2,
price: 99.99
});
// รับ results
const results = await EventManager.emit('validate:form', {
formData: {...}
});
const isValid = results.every(r => r === true);
// ส่งโดยไม่มี data
await EventManager.emit('app:ready');off(event, identifier?)
ยกเลิก listeners
const removed = EventManager.off(eventName, identifier);Parameters:
event(string) - ชื่อ event หรือ patternidentifier(string|function) - optional- ถ้าไม่ระบุ: ยกเลิกทั้งหมด
- string: ยกเลิกตาม ID
- function: ยกเลิกตาม callback reference
Returns: boolean - true ถ้ายกเลิกสำเร็จ
ตัวอย่าง:
// ยกเลิกทั้งหมด
EventManager.off('user:login');
// ยกเลิกเฉพาะ listener
const handler = (ctx) => console.log(ctx.data);
EventManager.on('event', handler);
EventManager.off('event', handler);
// ใช้ unsubscribe function
const unsubscribe = EventManager.on('event', handler);
unsubscribe();once(event, callback, options?)
ลงทะเบียน listener ที่ทำงานครั้งเดียว
const unsubscribe = EventManager.once(eventName, callback, options);เหมือนกับ on() แต่ตั้ง once: true อัตโนมัติ
ตัวอย่าง:
// ทำงานครั้งเดียว
EventManager.once('app:loaded', (context) => {
console.log('App loaded at:', context.timestamp);
});
// พร้อม priority
EventManager.once('init:complete', handler, {
priority: 100
});Patterns และ Wildcards
EventManager รองรับ pattern matching สำหรับ event names
Wildcard Operators
| Operator | ความหมาย | ตัวอย่าง | ตรงกับ |
|---|---|---|---|
* |
ตรงกับอักขระใดๆ จำนวนเท่าไหร่ก็ได้ | user:* |
user:login, user:logout, user:register |
? |
ตรงกับอักขระเดี่ยว 1 ตัว | user:log?n |
user:login |
+ |
ตรงกับอักขระก่อนหน้า 1 ครั้งขึ้นไป | (ใช้กับ regex) | - |
| |
OR operator | (ใช้กับ regex) | - |
ตัวอย่าง Patterns
// ตรงกับ events ทั้งหมดที่ขึ้นต้นด้วย user:
EventManager.on('user:*', (context) => {
console.log('User event:', context.event);
});
// ตรงกับ notification ทุกประเภท
EventManager.on('notification:*', (context) => {
showNotification(context.data.message);
});
// ตรงกับ events หลายระดับ
EventManager.on('api:*:success', (context) => {
// ตรงกับ api:user:success, api:product:success, etc.
console.log('API success:', context.event);
});
// ใช้ single character wildcard
EventManager.on('page:?', (context) => {
// ตรงกับ page:1, page:2, page:a, page:b
});Custom Regex Patterns
// ใช้ regex pattern โดยตรง
EventManager.on(/^user:(login|logout|register)$/, (context) => {
console.log('Auth event:', context.event);
});
// Pattern ซับซ้อน
EventManager.on(/^api:\w+:(get|post|put|delete)$/, (context) => {
logApiCall(context);
});Pattern Priority
เมื่อ event ตรงกับหลาย patterns:
- Exact match ทำก่อน
- Pattern matches ทำตาม priority
- Listeners ภายใน pattern เดียวกันทำตาม priority
// Exact match - priority สูงสุด
EventManager.on('user:login', handler1, {priority: 10});
// Pattern match - ทำหลัง exact match
EventManager.on('user:*', handler2, {priority: 100}); // ถึงแม้ priority สูงกว่า
// เมื่อ emit('user:login')
// จะทำ: handler1 -> handler2Event Groups
จัดกลุ่ม events เพื่อจัดการร่วมกัน
สร้าง Group
// เพิ่ม listeners เข้า group
EventManager.on('notification:info', handler1, {group: 'notifications'});
EventManager.on('notification:warning', handler2, {group: 'notifications'});
EventManager.on('notification:error', handler3, {group: 'notifications'});Emit Group
ส่ง events ทั้งหมดใน group
// ส่งไปยังทุก event ใน group
const results = await EventManager.emitGroup('notifications', {
message: 'System update completed',
level: 'info'
});Use Cases สำหรับ Groups
1. Notification System
// จัดกลุ่ม notification handlers
EventManager.on('notification:info', showInfoNotification, {
group: 'notifications'
});
EventManager.on('notification:success', showSuccessNotification, {
group: 'notifications'
});
EventManager.on('notification:error', showErrorNotification, {
group: 'notifications'
});
// ส่งไปยังทั้ง group
await EventManager.emitGroup('notifications', {
message: 'Operation completed'
});2. Analytics Tracking
// จัดกลุ่ม analytics events
EventManager.on('analytics:pageview', trackPageView, {
group: 'analytics'
});
EventManager.on('analytics:click', trackClick, {
group: 'analytics'
});
EventManager.on('analytics:conversion', trackConversion, {
group: 'analytics'
});
// Track all analytics
await EventManager.emitGroup('analytics', {
session: sessionId,
timestamp: Date.now()
});3. Feature Toggles
// จัดกลุ่ม feature events
EventManager.on('feature:enabled', enableFeature, {
group: 'features'
});
EventManager.on('feature:disabled', disableFeature, {
group: 'features'
});
// ส่งไปยัง features ทั้งหมด
await EventManager.emitGroup('features', {
feature: 'dark-mode',
enabled: true
});Priority System
กำหนดลำดับการทำงานของ listeners
Priority Values
- เลขมาก = priority สูง (ทำก่อน)
- เลขน้อย = priority ต่ำ (ทำหลัง)
- Default = 0
// Priority สูง - ทำก่อน
EventManager.on('data:process', validateData, {
priority: 100
});
// Priority กลาง
EventManager.on('data:process', transformData, {
priority: 50
});
// Priority ต่ำ - ทำหลัง
EventManager.on('data:process', saveData, {
priority: 10
});
// Default priority (0) - ทำหลังสุด
EventManager.on('data:process', logData);
// เมื่อ emit('data:process')
// จะทำตามลำดับ: validateData -> transformData -> saveData -> logDataUse Cases สำหรับ Priority
1. Data Processing Pipeline
// Validation ต้องทำก่อน
EventManager.on('form:submit', validateForm, {
priority: 100
});
// Transformation ทำหลัง validation
EventManager.on('form:submit', transformData, {
priority: 80
});
// Sanitization
EventManager.on('form:submit', sanitizeData, {
priority: 60
});
// บันทึกข้อมูล - ทำหลังสุด
EventManager.on('form:submit', saveToDatabase, {
priority: 10
});2. Authentication Flow
// ตรวจสอบ token ก่อน
EventManager.on('request:*', verifyToken, {
priority: 1000
});
// ตรวจสอบ permissions
EventManager.on('request:*', checkPermissions, {
priority: 900
});
// Rate limiting
EventManager.on('request:*', checkRateLimit, {
priority: 800
});
// ทำงานจริง - priority ต่ำกว่า
EventManager.on('request:create', createResource, {
priority: 100
});3. Error Handling
// Log error ก่อน
EventManager.on('error:*', logError, {
priority: 100
});
// แจ้งเตือน admin
EventManager.on('error:critical', notifyAdmin, {
priority: 90
});
// แสดง error ให้ user
EventManager.on('error:*', showErrorMessage, {
priority: 50
});Middleware
Intercept และแก้ไข events ก่อนส่งต่อไปยง listeners
การสร้าง Middleware
// Middleware แบบ function
EventManager.use((context) => {
console.log('Event:', context.event);
console.log('Data:', context.data);
// return false เพื่อหยุด event
if (context.data.blocked) {
return false;
}
// return true หรือ undefined เพื่อส่งต่อ
return true;
});
// Middleware แบบ object พร้อม hooks
EventManager.use({
beforeEmit: (context) => {
// ทำงานก่อน emit
console.log('Before emit:', context.event);
// แก้ไข data
context.data.timestamp = Date.now();
return true; // ส่งต่อ
},
afterEmit: (context) => {
// ทำงานหลัง emit
console.log('After emit:', context.event);
console.log('Results:', context.results);
}
});Middleware Hooks
| Hook | เมื่อใด | Use Case |
|---|---|---|
beforeEmit |
ก่อนส่ง event | Validation, logging, data transformation |
afterEmit |
หลังส่ง event เสร็จ | Logging results, cleanup, notifications |
ตัวอย่าง Middleware
1. Logging Middleware
EventManager.use({
beforeEmit: (context) => {
console.log(`[Event] ${context.event}`, {
timestamp: context.timestamp,
data: context.data
});
},
afterEmit: (context) => {
console.log(`[Event Complete] ${context.event}`, {
duration: Date.now() - context.timestamp,
results: context.results.length
});
}
});2. Authentication Middleware
EventManager.use((context) => {
// ตรวจสอบ authentication สำหรับ events ที่ต้อง auth
if (context.event.startsWith('api:')) {
if (!isAuthenticated()) {
console.error('Unauthorized event:', context.event);
return false; // block event
}
}
return true;
});3. Data Validation Middleware
EventManager.use({
beforeEmit: (context) => {
// Validate data structure
if (context.data && typeof context.data !== 'object') {
console.error('Invalid data type for event:', context.event);
return false;
}
// Add default values
context.data.timestamp = context.data.timestamp || Date.now();
context.data.source = context.data.source || 'app';
return true;
}
});4. Rate Limiting Middleware
const eventCounts = new Map();
EventManager.use((context) => {
const key = `${context.event}:${Date.now() / 1000 | 0}`;
const count = eventCounts.get(key) || 0;
if (count >= 100) {
console.warn('Rate limit exceeded for:', context.event);
return false;
}
eventCounts.set(key, count + 1);
return true;
});5. Error Tracking Middleware
EventManager.use({
afterEmit: (context) => {
// Track errors in results
const errors = context.results.filter(r => r instanceof Error);
if (errors.length > 0) {
console.error('Errors in event:', context.event, errors);
// Send to error tracking service
ErrorManager.handle(new Error('Event execution errors'), {
context: context.event,
data: {errors}
});
}
}
});Async Events
รองรับ asynchronous event handlers และ promises
Async Handlers
// Async handler
EventManager.on('user:register', async (context) => {
const {email, password} = context.data;
// Async operations
await createUserAccount(email, password);
await sendWelcomeEmail(email);
await logActivity('user:registered', email);
return {success: true, userId: 123};
}, {
async: true,
timeout: 10000 // 10 seconds timeout
});
// Emit และรอ results
const results = await EventManager.emit('user:register', {
email: 'user@example.com',
password: 'securepass'
});
console.log('Registration results:', results);Promise Handlers
EventManager.on('data:fetch', (context) => {
// Return promise
return fetch(context.data.url)
.then(res => res.json())
.then(data => {
console.log('Data fetched:', data);
return data;
});
}, {
async: true
});
// Emit และรอ
const results = await EventManager.emit('data:fetch', {
url: '/api/users'
});Timeout Configuration
// Global timeout (default: 5000ms)
await EventManager.init({
asyncTimeout: 10000
});
// Per-listener timeout
EventManager.on('heavy:operation', heavyHandler, {
async: true,
timeout: 30000 // 30 seconds
});Error Handling ใน Async
EventManager.on('risky:operation', async (context) => {
try {
const result = await riskyAsyncOperation(context.data);
return result;
} catch (error) {
console.error('Operation failed:', error);
return {error: error.message};
}
}, {
async: true,
onError: (error) => {
// Custom error handler
console.error('Async handler error:', error);
notifyAdmin(error);
}
});Parallel Async Execution
// หลาย async handlers ทำงานแบบ parallel
EventManager.on('data:sync', async (context) => {
await syncToDatabase(context.data);
return 'db-synced';
}, {async: true});
EventManager.on('data:sync', async (context) => {
await syncToCache(context.data);
return 'cache-synced';
}, {async: true});
EventManager.on('data:sync', async (context) => {
await syncToSearch(context.data);
return 'search-synced';
}, {async: true});
// รอทั้งหมดเสร็จ
const results = await EventManager.emit('data:sync', {
data: {...}
});
console.log('Sync results:', results);
// ['db-synced', 'cache-synced', 'search-synced']การจัดการ Context
Event context object ที่ส่งไปยัง listeners
Context Structure
{
event: string, // ชื่อ event
data: object, // ข้อมูลที่ส่งมา
timestamp: number, // เวลาที่ส่ง event
preventDefault: boolean, // flag สำหรับยกเลิก default behavior
stopPropagation: boolean, // flag สำหรับหยุดส่งต่อ
stopImmediatePropagation: boolean, // หยุดทันที
results: array, // results จาก listeners
source: any, // แหล่งที่มาของ event
path: array, // event hierarchy path
currentPath: string // current event path
}Control Propagation
EventManager.on('form:submit', (context) => {
// ตรวจสอบข้อมูล
if (!validateForm(context.data)) {
// หยุดการส่งต่อไปยัง listeners อื่น
context.stopPropagation = true;
return {valid: false};
}
return {valid: true};
}, {priority: 100});
EventManager.on('form:submit', (context) => {
// จะไม่ทำงานถ้า validation ไม่ผ่าน
saveForm(context.data);
});Prevent Default Behavior
EventManager.on('link:click', (context) => {
// ยกเลิก default behavior
context.preventDefault = true;
// ทำ custom behavior
handleCustomNavigation(context.data.url);
});Stop Immediate Propagation
EventManager.on('critical:error', (context) => {
// หยุด listeners อื่นทันที
context.stopImmediatePropagation = true;
// Handle critical error
handleCriticalError(context.data);
}, {priority: 1000});
// Listeners อื่นจะไม่ทำงาน
EventManager.on('critical:error', (context) => {
// จะไม่ถูกเรียก
logError(context.data);
});Access Event Hierarchy
EventManager.on('api:user:created', (context) => {
console.log('Event:', context.event);
// 'api:user:created'
console.log('Path:', context.path);
// ['api:user:created', 'api:user', 'api']
// listeners จะถูกเรียกตาม hierarchy
});
// Listeners ทั้งหมดเหล่านี้จะถูกเรียก:
EventManager.on('api:user:created', handler1);
EventManager.on('api:user', handler2);
EventManager.on('api', handler3);Return Values
EventManager.on('calculate:sum', (context) => {
const sum = context.data.numbers.reduce((a, b) => a + b, 0);
return sum; // return value จะถูกเก็บใน context.results
});
EventManager.on('calculate:sum', (context) => {
const avg = context.data.numbers.reduce((a, b) => a + b, 0) / context.data.numbers.length;
return avg;
});
// Emit และรับ results
const results = await EventManager.emit('calculate:sum', {
numbers: [1, 2, 3, 4, 5]
});
console.log('Results:', results); // [15, 3]Cleanup และ Memory Management
EventManager มีระบบ cleanup อัตโนมัติเพื่อจัดการหน่วยความจำ
Auto Cleanup
// เปิดใช้งาน cleanup (default: enabled)
await EventManager.init({
cleanup: {
enabled: true,
interval: 60000, // ตรวจสอบทุก 60 วินาที
maxAge: 1800000, // ลบ listeners เก่ากว่า 30 นาที
batchSize: 100 // ทำทีละ 100 รายการ
}
});Manual Cleanup
// ลบ listeners ที่ไม่ใช้แล้ว
EventManager.cleanup();
// Clear events ทั้งหมด
EventManager.clear();
// Destroy และ cleanup ทุกอย่าง
EventManager.destroy();Cleanup Stale Listeners
// ลบ listeners เก่าของ event เฉพาะ
EventManager.cleanupStaleListeners('old:event');Best Practices สำหรับ Memory Management
1. ใช้ Once สำหรับ One-time Events
// ❌ Bad: listener ไม่ถูกลบ
EventManager.on('app:initialized', handler);
// ✅ Good: ลบอัตโนมัติหลังทำงาน
EventManager.once('app:initialized', handler);2. Unsubscribe เมื่อไม่ใช้แล้ว
// ✅ Good: เก็บ unsubscribe function
const unsubscribe = EventManager.on('data:updated', handler);
// เมื่อ component ถูก destroy
componentWillUnmount() {
unsubscribe();
}3. ใช้ Groups สำหรับ Batch Cleanup
// เพิ่มเข้า group
EventManager.on('temp:event1', handler1, {group: 'temporary'});
EventManager.on('temp:event2', handler2, {group: 'temporary'});
EventManager.on('temp:event3', handler3, {group: 'temporary'});
// Cleanup ทั้ง group พร้อมกัน
// (ไม่มี API โดยตรง แต่สามารถใช้ off กับแต่ละ event)
EventManager.off('temp:event1');
EventManager.off('temp:event2');
EventManager.off('temp:event3');4. Monitor Listener Count
// ตรวจสอบจำนวน listeners
const info = EventManager.getEventInfo('user:login');
console.log('Listener count:', info.listenerCount);
// เตือนเมื่อมีเยอะเกินไป
if (info.listenerCount > 50) {
console.warn('Too many listeners for event:', 'user:login');
}Integration กับ Now.js
EventManager ถูก integrate กับ Now.js Framework
Auto-Registration
// EventManager ถูก register อัตโนมัติเมื่อโหลด
console.log(Now.managers.has('event')); // trueเข้าถึงผ่าน Now.js
// Method 1: Now.emit() wrapper
Now.emit('user:login', {userId: 123});
// Note: Now.emit() เพิ่ม timestamp อัตโนมัติ
// Method 2: ผ่าน Manager system
const eventManager = Now.getManager('event');
eventManager.emit('user:login', {userId: 123});
// Method 3: Direct access
EventManager.emit('user:login', {userId: 123});ใช้กับ Now.js Components
// ใน component
const MyComponent = {
async init() {
// ลงทะเบียน listeners
this.unsubscribe = EventManager.on('data:updated', (context) => {
this.handleDataUpdate(context.data);
});
},
handleDataUpdate(data) {
console.log('Data updated:', data);
this.render();
},
async destroy() {
// Cleanup เมื่อ destroy
if (this.unsubscribe) {
this.unsubscribe();
}
}
};
ComponentManager.define('my-component', MyComponent);Integration Events
Now.js ส่ง events เหล่านี้ผ่าน EventManager:
// App lifecycle events
EventManager.on('app:mounted', (context) => {
console.log('App mounted:', context.data.app);
});
EventManager.on('app:ready', (context) => {
console.log('App ready');
});
// Error events
EventManager.on('error', (context) => {
console.error('Error:', context.data.error);
});
// Performance events
EventManager.on('performance:recorded', (context) => {
console.log('Performance metric:', context.data);
});ตัวอย่างการใช้งาน
1. User Authentication Flow
// Listen สำหรับ login success
EventManager.on('auth:login:success', async (context) => {
const {user, token} = context.data;
// บันทึก token
localStorage.setItem('token', token);
// โหลดข้อมูล user
await loadUserData(user.id);
// Redirect
window.location.href = '/dashboard';
}, {
priority: 100
});
// Analytics tracking
EventManager.on('auth:*', (context) => {
analytics.track(context.event, context.data);
}, {
priority: 10
});
// ส่ง login event
await EventManager.emit('auth:login:success', {
user: {id: 123, name: 'John'},
token: 'abc123'
});2. Form Validation Pipeline
// Validation - priority สูงสุด
EventManager.on('form:submit', (context) => {
const errors = validateForm(context.data.formData);
if (errors.length > 0) {
context.stopPropagation = true;
return {valid: false, errors};
}
return {valid: true};
}, {
priority: 100
});
// Sanitization
EventManager.on('form:submit', (context) => {
context.data.formData = sanitizeFormData(context.data.formData);
}, {
priority: 80
});
// Transform
EventManager.on('form:submit', (context) => {
context.data.formData = transformData(context.data.formData);
}, {
priority: 60
});
// Save - priority ต่ำสุด
EventManager.on('form:submit', async (context) => {
await saveToDatabase(context.data.formData);
return {saved: true};
}, {
priority: 10,
async: true
});
// Submit form
const results = await EventManager.emit('form:submit', {
formData: {...}
});3. Real-time Notification System
// จัดกลุ่ม notification handlers
EventManager.on('notification:info', (context) => {
showToast(context.data.message, 'info');
}, {group: 'notifications'});
EventManager.on('notification:success', (context) => {
showToast(context.data.message, 'success');
}, {group: 'notifications'});
EventManager.on('notification:warning', (context) => {
showToast(context.data.message, 'warning');
}, {group: 'notifications'});
EventManager.on('notification:error', (context) => {
showToast(context.data.message, 'error');
}, {group: 'notifications'});
// ส่ง notification
await EventManager.emit('notification:success', {
message: 'Data saved successfully!'
});
// หรือส่งไปยังทุก notification
await EventManager.emitGroup('notifications', {
message: 'System updated'
});4. Shopping Cart Events
// Item added
EventManager.on('cart:item-added', async (context) => {
const {product, quantity} = context.data;
// อัพเดท UI
updateCartBadge();
// บันทึก
await saveCart();
// แสดง notification
await EventManager.emit('notification:success', {
message: `Added ${product.name} to cart`
});
});
// Item removed
EventManager.on('cart:item-removed', async (context) => {
updateCartBadge();
await saveCart();
});
// Quantity changed
EventManager.on('cart:quantity-changed', async (context) => {
updateCartTotal();
await saveCart();
});
// ใช้ pattern สำหรับ analytics
EventManager.on('cart:*', (context) => {
analytics.track(context.event, context.data);
});
// ใช้งาน
await EventManager.emit('cart:item-added', {
product: {id: 123, name: 'Product A'},
quantity: 2
});5. API Request Lifecycle
// Before request
EventManager.on('api:request:before', (context) => {
// แสดง loading
showLoadingSpinner();
// เพิ่ม headers
context.data.headers = {
...context.data.headers,
'Authorization': `Bearer ${getToken()}`
};
}, {
priority: 100
});
// Success
EventManager.on('api:request:success', async (context) => {
hideLoadingSpinner();
await EventManager.emit('notification:success', {
message: 'Request successful'
});
});
// Error
EventManager.on('api:request:error', async (context) => {
hideLoadingSpinner();
await EventManager.emit('notification:error', {
message: context.data.error.message
});
// Retry logic
if (context.data.error.code === 401) {
await refreshToken();
return {retry: true};
}
});
// After request (always)
EventManager.on('api:request:after', (context) => {
logApiCall(context.data);
});6. WebSocket Message Handler
// เชื่อมต่อ WebSocket
const ws = new WebSocket('ws://localhost:8080');
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
// ส่งต่อเป็น events
EventManager.emit(`ws:${message.type}`, message.data);
};
// จัดการ message types
EventManager.on('ws:user-online', (context) => {
updateUserStatus(context.data.userId, 'online');
});
EventManager.on('ws:new-message', (context) => {
displayMessage(context.data);
playNotificationSound();
});
EventManager.on('ws:typing', (context) => {
showTypingIndicator(context.data.userId);
});
// Pattern สำหรับ logging
EventManager.on('ws:*', (context) => {
console.log('WebSocket event:', context.event, context.data);
});7. Theme Switcher
// Listen สำหรับ theme changes
EventManager.on('theme:changed', (context) => {
const {theme} = context.data;
// อัพเดท DOM
document.body.className = theme;
// บันทึก preference
localStorage.setItem('theme', theme);
// อัพเดท components
reloadComponents();
}, {
once: false
});
// Switch theme
async function switchTheme(theme) {
await EventManager.emit('theme:changed', {theme});
}
// Toggle dark mode
document.getElementById('dark-mode-toggle').addEventListener('click', () => {
const currentTheme = document.body.className;
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
switchTheme(newTheme);
});8. Data Synchronization
// Sync เมื่อข้อมูลเปลี่ยน
EventManager.on('data:changed', async (context) => {
const {entity, data} = context.data;
// Sync ไปยังหลาย destinations พร้อมกัน
await Promise.all([
syncToLocalStorage(entity, data),
syncToIndexedDB(entity, data),
syncToServer(entity, data)
]);
return {synced: true};
}, {
async: true,
timeout: 10000
});
// Conflict resolution
EventManager.on('data:conflict', async (context) => {
const {local, remote} = context.data;
// แสดง UI สำหรับ resolve conflict
const resolved = await showConflictDialog(local, remote);
return {resolved};
}, {
async: true
});
// ใช้งาน
await EventManager.emit('data:changed', {
entity: 'user',
data: {id: 123, name: 'John Updated'}
});9. Feature Toggle System
// Define features
const features = new Map();
// Enable feature
EventManager.on('feature:enable', (context) => {
const {feature} = context.data;
features.set(feature, true);
// Reload affected components
reloadFeatureDependentComponents(feature);
console.log('Feature enabled:', feature);
});
// Disable feature
EventManager.on('feature:disable', (context) => {
const {feature} = context.data;
features.set(feature, false);
reloadFeatureDependentComponents(feature);
console.log('Feature disabled:', feature);
});
// Check feature
function isFeatureEnabled(feature) {
return features.get(feature) || false;
}
// Toggle
async function toggleFeature(feature) {
const enabled = isFeatureEnabled(feature);
const event = enabled ? 'feature:disable' : 'feature:enable';
await EventManager.emit(event, {feature});
}
// ใช้ใน code
if (isFeatureEnabled('dark-mode')) {
enableDarkMode();
}10. Performance Monitoring
// Track performance metrics
const performanceMetrics = [];
EventManager.use({
beforeEmit: (context) => {
context.startTime = performance.now();
},
afterEmit: (context) => {
const duration = performance.now() - context.startTime;
performanceMetrics.push({
event: context.event,
duration,
timestamp: context.timestamp,
listenerCount: context.results.length
});
// Warn ถ้าช้าเกินไป
if (duration > 100) {
console.warn('Slow event:', context.event, `${duration}ms`);
}
}
});
// Report metrics
function getPerformanceReport() {
const avgDuration = performanceMetrics.reduce((sum, m) =>
sum + m.duration, 0) / performanceMetrics.length;
const slowEvents = performanceMetrics
.filter(m => m.duration > 100)
.sort((a, b) => b.duration - a.duration);
return {
totalEvents: performanceMetrics.length,
averageDuration: avgDuration,
slowEvents: slowEvents.slice(0, 10)
};
}
// ดู report
console.log('Performance Report:', getPerformanceReport());API Reference
Configuration
{
asyncTimeout: number, // Timeout สำหรับ async handlers (ms) default: 5000
maxListeners: number, // จำนวน listeners สูงสุดต่อ event default: 2048
traceEvents: boolean, // เก็บ history สำหรับ debug default: false
cleanup: {
enabled: boolean, // เปิดใช้ auto cleanup default: true
interval: number, // ตรวจสอบทุก x ms default: 60000
maxAge: number, // ลบ listeners เก่ากว่า x ms default: 1800000
batchSize: number // ทำทีละ x รายการ default: 100
}
}Methods
Core Methods
// Initialize
init(options?: object): Promise<EventManager>
// Register listener
on(event: string, callback: function, options?: object): function
// Register one-time listener
once(event: string, callback: function, options?: object): function
// Emit event
emit(event: string, data?: object): Promise<array>
// Remove listener(s)
off(event: string, identifier?: string|function): boolean
// Emit to group
emitGroup(group: string, data?: object): Promise<array>Middleware Methods
// Add middleware
use(middleware: function|object): EventManager
// Run middleware hook
runMiddleware(hook: string, context: object): Promise<boolean>Pattern Methods
// Create regex pattern from string
createPattern(pattern: string): RegExp
// Add pattern listener
addPatternListener(pattern: string, callback: function, options?: object): function
// Remove pattern listener
removePatternListener(pattern: string, identifier?: string|function): boolean
// Check if string is pattern
isPattern(str: string): booleanGroup Methods
// Add listener to group
addToGroup(group: string, event: string, listener: object): void
// Remove listener from group
removeFromGroup(group: string, event: string, listener: object): voidCleanup Methods
// Manual cleanup
cleanup(): void
// Cleanup specific event
cleanupStaleListeners(event: string): void
// Clear all
clear(): void
// Destroy
destroy(): voidInfo Methods
// Get event info
getEventInfo(event: string): object
// Get debug info
getDebugInfo(): objectContext Object
{
event: string, // Event name
data: object, // Event data
timestamp: number, // Emit timestamp
preventDefault: boolean, // Prevent default flag
stopPropagation: boolean, // Stop propagation flag
stopImmediatePropagation: boolean,// Stop immediately flag
results: array, // Results from listeners
source: any, // Event source
path: array, // Event hierarchy path
currentPath: string // Current path in hierarchy
}Listener Options
{
priority: number, // Priority (higher = first) default: 0
once: boolean, // Run once and remove default: false
async: boolean, // Async handler default: false
group: string, // Group name default: null
timeout: number, // Async timeout (ms) default: 5000
maxRetries: number, // Max retry attempts default: 1
onError: function // Error handler default: null
}Best Practices
1. ✅ ใช้ Namespaced Events
// ❌ Bad: ชื่อทั่วไป
EventManager.on('success', handler);
EventManager.on('error', handler);
// ✅ Good: ชื่อชัดเจน
EventManager.on('auth:login:success', handler);
EventManager.on('api:user:error', handler);2. ✅ Cleanup Listeners
// ❌ Bad: ไม่ cleanup
EventManager.on('data:updated', handler);
// ✅ Good: cleanup เมื่อไม่ใช้
const unsubscribe = EventManager.on('data:updated', handler);
// เมื่อ component destroy
componentWillUnmount() {
unsubscribe();
}3. ✅ ใช้ Once สำหรับ One-time Events
// ❌ Bad: ต้อง off เอง
EventManager.on('app:ready', handler);
EventManager.off('app:ready', handler);
// ✅ Good: ลบอัตโนมัติ
EventManager.once('app:ready', handler);4. ✅ ใช้ Priority อย่างเหมาะสม
// ❌ Bad: ไม่มี priority
EventManager.on('form:submit', saveData);
EventManager.on('form:submit', validateData);
// ✅ Good: validate ก่อน save
EventManager.on('form:submit', validateData, {priority: 100});
EventManager.on('form:submit', saveData, {priority: 10});5. ✅ Handle Errors ใน Async
// ❌ Bad: ไม่จัดการ errors
EventManager.on('data:fetch', async (context) => {
const data = await fetch(context.data.url);
return data.json();
}, {async: true});
// ✅ Good: มี error handling
EventManager.on('data:fetch', async (context) => {
try {
const data = await fetch(context.data.url);
return await data.json();
} catch (error) {
console.error('Fetch error:', error);
return {error: error.message};
}
}, {async: true});6. ✅ ใช้ Patterns อย่างชาญฉลาด
// ❌ Bad: ลงทะเบียนทีละ event
EventManager.on('notification:info', handler);
EventManager.on('notification:success', handler);
EventManager.on('notification:warning', handler);
EventManager.on('notification:error', handler);
// ✅ Good: ใช้ pattern
EventManager.on('notification:*', handler);7. ✅ ใช้ Groups สำหรับ Related Events
// ✅ Good: จัดกลุ่ม related events
EventManager.on('analytics:pageview', trackPageView, {
group: 'analytics'
});
EventManager.on('analytics:click', trackClick, {
group: 'analytics'
});
// Emit ทั้ง group
await EventManager.emitGroup('analytics', data);8. ✅ Document Event Names
// ✅ Good: เขียน documentation
/**
* Events:
* - user:login - เมื่อ user login สำเร็จ
* - user:logout - เมื่อ user logout
* - user:register - เมื่อ user ลงทะเบียนใหม่
*
* Data structure:
* {
* userId: number,
* username: string,
* email: string
* }
*/
const AuthEvents = {
LOGIN: 'user:login',
LOGOUT: 'user:logout',
REGISTER: 'user:register'
};
// ใช้ constants
EventManager.on(AuthEvents.LOGIN, handler);9. ✅ Test Event Flow
// ✅ Good: เขียน tests
describe('EventManager', () => {
it('should handle user login', async () => {
const handler = jest.fn();
EventManager.on('user:login', handler);
await EventManager.emit('user:login', {
userId: 123
});
expect(handler).toHaveBeenCalled();
expect(handler).toHaveBeenCalledWith(
expect.objectContaining({
event: 'user:login',
data: {userId: 123}
})
);
});
});10. ✅ Monitor Performance
// ✅ Good: ติดตาม performance
EventManager.use({
beforeEmit: (context) => {
context._startTime = performance.now();
},
afterEmit: (context) => {
const duration = performance.now() - context._startTime;
if (duration > 100) {
console.warn('Slow event:', context.event, `${duration}ms`);
}
}
});สรุป
เมื่อใดควรใช้ EventManager
| Use Case | ใช้ EventManager? | หมายเหตุ |
|---|---|---|
| สื่อสารระหว่าง Components | ✅ ใช่ | Event Bus Pattern |
| จัดการ Application Events | ✅ ใช่ | Custom events |
| Decouple Modules | ✅ ใช่ | Loose coupling |
| Pattern Matching | ✅ ใช่ | Wildcards support |
| Event Groups | ✅ ใช่ | Built-in support |
| Middleware | ✅ ใช่ | Interceptors |
| จัดการ DOM Events | ❌ ไม่ | ใช้ EventSystemManager |
| Event Delegation | ❌ ไม่ | ใช้ EventSystemManager |
คุณสมบัติหลัก
| คุณสมบัติ | รองรับ | หมายเหตุ |
|---|---|---|
| Event Bus | ✅ | Publish/Subscribe |
| Pattern Matching | ✅ | Wildcards, Regex |
| Event Hierarchy | ✅ | Dot notation |
| Event Groups | ✅ | Grouped events |
| Priority | ✅ | Ordered execution |
| Middleware | ✅ | beforeEmit, afterEmit |
| Async Support | ✅ | Promises, Async/Await |
| Context Control | ✅ | stopPropagation, preventDefault |
| Auto Cleanup | ✅ | Memory management |
| Debug Mode | ✅ | Event history |