
Chào các "coder nhí" Gen Z! Hôm nay, Anh Creyt sẽ bật mí cho mấy đứa một "bí kíp" trong Node.js mà nhiều khi mấy đứa dùng mà không biết tên, hoặc biết tên mà chưa hiểu hết độ "chill" của nó: events.EventEmitter.
1. EventEmitter là gì mà "hot" thế?
Đầu tiên, mấy đứa cứ hình dung thế này: EventEmitter nó như một ông bầu sự kiện (event organizer) chuyên nghiệp vậy đó. Ông bầu này có nhiệm vụ đứng ra "kêu gọi" hoặc "thông báo" khi có một sự kiện gì đó vừa xảy ra. Còn mấy đứa, những "khán giả" hay "nghệ sĩ" quan tâm, chỉ cần đăng ký với ông bầu là: "Ê, khi nào có cái sự kiện A thì hú tui cái nha!" hoặc "Khi nào có sự kiện B thì tui sẽ làm cái này, cái kia!".
Trong thế giới code, EventEmitter là một module "cốt cán" của Node.js, cho phép các đối tượng (objects) có thể "phát ra" (emit) các sự kiện có tên, và các đối tượng khác có thể "lắng nghe" (listen) các sự kiện đó rồi thực thi một hành động nào đó. Nó giúp mấy đứa tạo ra một hệ thống giao tiếp giữa các phần khác nhau của ứng dụng mà không cần chúng phải biết trực tiếp về nhau – nghe có vẻ phức tạp nhưng thực ra là đang giúp code của mình "healthy và balance" hơn đó!
Để làm gì? Đơn giản là để code của mấy đứa "linh hoạt" hơn, "dễ thở" hơn. Thay vì một module cứ phải "gọi thẳng mặt" module khác để nhờ vả, thì giờ nó chỉ cần "hô to" một sự kiện lên. Ai quan tâm thì tự động làm, không quan tâm thì thôi. Giống như mấy đứa đăng story trên Instagram vậy, ai follow thì thấy, ai không follow thì chịu. Nó giúp giảm sự phụ thuộc (decoupling) giữa các thành phần, làm cho code dễ bảo trì, mở rộng và test hơn.
2. Code Ví Dụ Minh Họa: "Bữa Tiệc" Bắt Đầu!
Để dễ hình dung, Anh Creyt sẽ tạo một "ông bầu" đơn giản chuyên tổ chức các buổi "party" nho nhỏ:
const EventEmitter = require('events');
// Khởi tạo ông bầu sự kiện của chúng ta
class PartyOrganizer extends EventEmitter {
constructor() {
super();
this.partyCount = 0;
}
// Phương thức để "tổ chức" một buổi party
organizeParty(name, theme) {
this.partyCount++;
console.log(`\n🎉 Anh Bầu Creyt: Chuẩn bị "quẩy" party mới: ${name} với chủ đề: ${theme}!\n`);
// Phát ra sự kiện 'newParty' kèm theo thông tin party
this.emit('newParty', { name, theme, id: this.partyCount });
// Thỉnh thoảng, có party "quẩy" hơi lố, phát ra lỗi
if (this.partyCount % 3 === 0) {
this.emit('error', new Error('Party "quẩy" quá đà, bị hàng xóm "nhắc nhở" rồi!'));
}
}
// Phương thức để kết thúc party
endParty(id) {
console.log(`\n😴 Anh Bầu Creyt: Party số ${id} đã "tan cuộc"! Hẹn gặp lại!\n`);
this.emit('partyEnded', id);
}
}
// Tạo một "ông bầu" cụ thể
const creytOrganizer = new PartyOrganizer();
// Các "khán giả"/"nghệ sĩ" đăng ký lắng nghe sự kiện
// 1. Bạn A: Mỗi khi có party mới, bạn A sẽ "check-in"
creytOrganizer.on('newParty', (partyInfo) => {
console.log(`\n📸 Bạn A: "Check-in" party ${partyInfo.name} - chủ đề ${partyInfo.theme}! #PartyVibes`);
});
// 2. Bạn B: Chỉ quan tâm đến party đầu tiên thôi, sau đó "out kèo"
creytOrganizer.once('newParty', (partyInfo) => {
console.log(`\n🥳 Bạn B: "Quẩy" hết mình ở party đầu tiên: ${partyInfo.name}! Sau đó "về ngủ"...`);
});
// 3. Bạn C: Chuyên đi "dọn dẹp" sau khi party tan
const cleanUpCrew = (partyId) => {
console.log(`\n🧹 Bạn C: Đã dọn dẹp xong party số ${partyId}! Sẵn sàng cho lần tới.`);
};
creytOrganizer.on('partyEnded', cleanUpCrew);
// 4. Luôn luôn lắng nghe sự kiện "error" để xử lý những pha "quẩy" quá đà
creytOrganizer.on('error', (err) => {
console.error(`\n🚨 Cảnh báo từ Anh Bầu: Có lỗi xảy ra trong quá trình tổ chức: ${err.message}`);
});
// Bắt đầu tổ chức các buổi party
creytOrganizer.organizeParty('Summer Chill', 'Tropical');
creytOrganizer.organizeParty('Halloween Blast', 'Spooky');
creytOrganizer.endParty(1);
creytOrganizer.organizeParty('New Year Rave', 'Futuristic');
creytOrganizer.endParty(3);
// Nếu bạn C không muốn dọn dẹp nữa (ví dụ: bận đi chơi)
// creytOrganizer.removeListener('partyEnded', cleanUpCrew);
// console.log('\nBạn C đã "nghỉ việc" dọn dẹp.');
creytOrganizer.organizeParty('Birthday Bash', 'Surprise');
Trong ví dụ trên:
PartyOrganizerkế thừa từEventEmitter, nên nó có thểemitvàoncác sự kiện.organizePartylà phương thức "phát ra" sự kiện'newParty'và cả'error'nếu có "sự cố".on('newParty', ...)là cách "đăng ký" để lắng nghe sự kiện'newParty'. Mỗi khi sự kiện này đượcemit, hàm callback sẽ được gọi.once('newParty', ...)cũng lắng nghe, nhưng chỉ được gọi một lần duy nhất sau đó tự động "hủy đăng ký".on('error', ...)là một sự kiện đặc biệt. Nếu mộtEventEmitterphát ra sự kiện'error'mà không có listener nào cho nó, Node.js sẽ "crash" (ném ra một uncaught exception). Vì vậy, luôn luôn lắng nghe'error'là một best practice cực kỳ quan trọng!removeListener(hoặcoff) dùng để "hủy đăng ký" một listener cụ thể.removeAllListenersthì "hủy" tất cả listener cho một sự kiện, hoặc tất cả các sự kiện.

3. Mẹo (Best Practices) Để "Flex" Code Với EventEmitter
Anh Creyt có vài "mẹo vặt" để mấy đứa dùng EventEmitter trông "pro" hơn:
- Đặt tên sự kiện rõ ràng, dễ hiểu: Đừng đặt tên kiểu
'e1','evt2'. Hãy dùng những cái tên có nghĩa như'userLoggedIn','dataReceived','paymentProcessed'. Giống như đặt tên hashtag cho story vậy, dễ tìm, dễ hiểu. - Luôn lắng nghe sự kiện
'error': Đây là "luật bất thành văn" luôn! NếuEventEmittercủa mấy đứaemit('error', someError)mà không aion('error', ...)thì ứng dụng của mấy đứa sẽ "toang" ngay lập tức. Cứ coi như'error'là "còi báo động" vậy, phải có người trực nghe chứ! - Cẩn thận với Memory Leaks: Nếu mấy đứa cứ
onmột đống listener mà không bao giờremoveListenerkhi không cần nữa, đặc biệt trong các ứng dụng chạy lâu dài, thì có thể dẫn đến rò rỉ bộ nhớ (memory leak). Giống như đi party mà cứ giữ vé VIP của tất cả các buổi party mãi không chịu bỏ vậy, tủ đồ sẽ chật cứng! - Dùng
oncekhi chỉ cần phản ứng một lần: Khi một hành động chỉ cần xảy ra đúng một lần khi sự kiện được kích hoạt (ví dụ: thiết lập kết nối lần đầu),oncelà lựa chọn hoàn hảo. Nó tự động "dọn dẹp" sau khi dùng. - Đừng lạm dụng:
EventEmittermạnh mẽ thật, nhưng đừng biến mọi thứ thành sự kiện. Nếu chỉ là một lời gọi hàm đơn giản, hãy dùng hàm bình thường. Đừng "làm màu" quá mức cần thiết.
4. Thực Tế Áp Dụng: EventEmitter "Khoe Sắc" Ở Đâu?
EventEmitter không phải là thứ xa lạ đâu, nó là "xương sống" của rất nhiều module "xịn xò" trong Node.js:
- HTTP Servers: Khi mấy đứa tạo một server với
http.createServer(), cái server đó chính là mộtEventEmitter. Nóemit('request', req, res)mỗi khi có yêu cầu HTTP đến. Nghe quen chưa? - Streams (File I/O, Network): Khi đọc/ghi file (
fs.createReadStream,fs.createWriteStream) hay xử lý dữ liệu mạng, các đối tượng Stream này cũng làEventEmitter. Chúngemit('data')khi có dữ liệu mới,emit('end')khi kết thúc,emit('error')khi có lỗi. - WebSockets: Các thư viện WebSocket như
wshaysocket.iocũng dùngEventEmitter"nặng đô" để quản lý các sự kiện kết nối, nhận/gửi tin nhắn, đóng kết nối. - Xây dựng hệ thống thông báo tùy chỉnh: Tưởng tượng mấy đứa có một ứng dụng thương mại điện tử. Khi có đơn hàng mới, thay vì phải gọi trực tiếp hàm gửi email, hàm gửi SMS, hàm cập nhật database, mấy đứa chỉ cần
emit('orderPlaced', orderDetails). Rồi các module khác sẽ lắng nghe và tự động xử lý phần việc của mình. Đẹp không?
5. Nên Dùng Cho Case Nào & "Thử Nghiệm" Đã Từng
Anh Creyt đã từng "thử nghiệm" EventEmitter trong rất nhiều trường hợp và thấy nó "phát huy" tối đa sức mạnh khi:
- Cần decoupling giữa các module: Khi một module cần thông báo cho nhiều module khác về một sự kiện mà không muốn biết cụ thể module nào sẽ phản ứng. Ví dụ, module xử lý thanh toán chỉ cần
emit('paymentSuccess'), các moduleEmailService,LogService,InventoryServicesẽ tự động lắng nghe và làm việc của mình. - Xử lý các tác vụ bất đồng bộ (asynchronous) dài hạn: Khi có một tiến trình cần nhiều bước và các bước đó có thể hoàn thành vào các thời điểm khác nhau. Ví dụ, một quá trình xử lý ảnh mất thời gian, nó có thể
emit('processingStarted'),emit('progress', percentage),emit('processingComplete', imageUrl). Client có thể lắng nghe các sự kiện này để cập nhật UI. - Xây dựng Plugin/Middleware: Khi mấy đứa muốn tạo một kiến trúc mở, nơi người dùng hoặc các module khác có thể "gắn" thêm chức năng vào các "điểm nóng" (hooks) của ứng dụng. Giống như các plugin của WordPress vậy đó, nó "chờ" sự kiện
post_publishedđể làm thêm vài trò.
EventEmitter là một công cụ cực kỳ mạnh mẽ và linh hoạt trong Node.js. Nắm vững nó, mấy đứa sẽ có thể viết ra những ứng dụng "chất lượng cao", dễ mở rộng và bảo trì hơn rất nhiều. Cứ coi nó như "người điều phối" mọi hoạt động trong "bữa tiệc code" của mấy đứa vậy. "Quẩy" hết mình nhưng nhớ là phải "quẩy" có kỷ luật nha!
Thuộc Series: Nodejs
Bài giảng này được tự động xuất bản ngẫu nhiên từ thư viện kiến thức. Đừng quên đón xem các Từ khoá Hướng Dẫn tiếp theo nhé!