Trong Sub-Series JavaScript trước đó, chúng ta đã có một bài viết về chủ đề Event & Listener và đã biết về hai kiểu sự kiện:
- Một là các sự kiện mô tả thao tác người dùng như
click,scroll, v.v… - Hai là các sự kiện được tạo ra bởi code phần mềm nhằm mục đích nào đó.
Và trong môi trường trình duyệt web, trong một sự kiện sẽ có sự tham gia của ba đối tượng:
Đầu tiên, là một object EventTarget – là đối tượng mà trình duyệt web sẽ gửi một sự kiện tới. Các object EventTarget được dựng sẵn trong môi trường trình duyệt web chính là các object Element mô tả các phần tử HTML. Ngoài ra thì chúng ta cũng có thể tạo ra các class mở rộng EventTarget để sử dụng nếu cần thiết.
Thứ hai, là một object Event – chứa thông tin mô tả về sự kiện đó, và sẽ được trình duyệt web hoặc code do chúng ta viết – gửi tới EventTarget bằng phương thức dựng sẵn target.dispatch(event).
Thứ ba, là các object EventListener – thực ra là các hàm xử lý sự kiện mà chúng ta gắn vào các EventTarget bằng phương thức target.addEventListener(eventName, listener). Các hàm này sẽ được kích hoạt bởi phương thức .dispatch ở trên và nhận được object Event để có thông tin mô tả thêm về sự kiện.
Và chủ đề mới của chúng ta đang hướng đến trong bài viết này, trên nền NodeJS, thực ra cũng không có gì mới mẻ so với những thứ mà chúng ta đã biết về Event trong lập trình nói chung.
Một sự kiện trong NodeJS được phát động như thế nào?
Hoàn toàn tương đồng với cách mà một sự kiện được phát động trong môi trường web, một sự kiện trong môi trường NodeJS được phát động tại vị trí của một object khởi điểm. Tuy nhiên NodeJS không gọi object này là “mục tiêu gửi sự kiện tới” EventTarget mà thay vào đó lại gọi là “chủ thể phát động sự kiện” EventEmitter.
Khác biệt về cách đặt tên này có lẽ là vì khi sử dụng một EventEmitter để phát động một sự kiện, chúng ta không cần tạo ra một object mô tả sự kiện trước đó để gửi tới mà thay vào đó thì chỉ cần .emit() tên của sự kiện là đủ.
const{ EventEmitter }=require("events");classDatabaseextendsEventEmitter{constructor(...params){super(...params)this.data =[];}insert(value){this.data.push(value);this.emit("insert", value,this.data);}}// Database/* --- Init Database */var db =newDatabase();/* --- Add Listener */
db.on("insert",(value, data)=>{
console.log("- - - - - - - - -");
console.log("Listener is activated");
console.log(`Inserted: ${value}`);
console.log(data);});/* --- Insert Something */
db.insert(1001);
db.insert("Infinity");
node emitter.js
- - - - - - - - -
Listener is activated
Inserted: 1001
[ 1001 ]
- - - - - - - - -
Listener is activated
Inserted: Infinity
[ 1001, 'Infinity' ]
Ở đây tất cả các giá trị được truyền vào phương thức .emit theo sau tên sự kiện sẽ được truyền cho các hàm xử lý sự kiện listener theo đúng thứ tự xuất hiện, và không giới hạn về số lượng tham số mà chúng ta muốn sử dụng để tạo bền mặt giao tiếp giữa các listener và emitter.
Các Emitter dựng sẵn
Giống với trong môi trường trình duyệt web, class EventTarget được sử dụng để tạo ra một phần giao diện lập trình của các object mô tả các phần tử HTML; Thì trong môi trường NodeJS, class EventEmitter được sử dụng để tạo ra một phần giao diện lập trình của các module và các class dựng sẵn khác.
Vì vậy nên khi nhìn vào tài liệu của một module bất kỳ, bạn sẽ luôn thấy có liệt kê tên của một vài kiểu sự kiện trong các class ngay ở phần Table of Content với cú pháp chung là Event: 'tên-event'. Và khi sử dụng các chức năng tiện ích do các class này cung cấp, chúng ta chỉ cần .on() để gắn các hàm xử lý sự kiện listener vào các emitter này với các tên sự kiện mô tả trong tài liệu.
Quản lý các Listener
Ngoài phương thức .on(), thì các emitter còn được thiết kế với các phương thức quản lý các hàm xử lý sự kiện khác nữa. Ở đây mình sẽ liệt kê một vài liên kết về các phương thức quản lý các listener để bạn tiện tham khảo:
emitter.on(eventName, listener)– gắn một hàm xử lý cho một sự kiện.emitter.once(eventName, listener)– giống với.on()tuy nhiên hàm xử lý sự kiện ở đây sẽ chỉ được kích hoạt 1 lần duy nhất và được tách khỏiemitterngay trước thời điểm kích hoạt.emitter.off(eventName, listener)– tách một hàm xử lý sự kiện khỏiemitter.
Một số phương thức khác dành cho nhu cầu quản lý đa dạng hơn nếu bạn thiết kế logic xử lý của phần mềm mà bạn đang viết xoay quanh các sự kiện.
emitter.eventNames– Xem tên của tất cả các kiểu sự kiện đã được gắn vàoemitter.emitter.listeners(eventName)– Truy xuất tất cả cáclistenerđã được gắn với tên sự kiệneventName.emitter.addListener(eventName, listener)– Tương tự với phương thức.on()ở trên. Hàm sử lý sự kiện mới sẽ được gắn vào cuối danh sáchlistenerstương ứng vớieventName.emitter.prependListener(eventName, listener)– Tương tự với phương thức.on(). Nhưng hàm sử lý sự kiện mới sẽ được gắn vào đầu danh sáchlistenerstương ứng vớieventName.emitter.prependOnceListener(eventName, listener)– Tương tự với phương thức.once()ở trên. Ồ… như vậy là các hàm xử lý sự kiện 1-lần sẽ luôn được gắn vào đầu danh sáchlistenerscủa mỗi kiểuevent.emitter.removeListener(eventName, listener)– Tương tự với phương thức.off()ở trên.emitter.removeAllListeners([eventName])– Tách rời cả cáclistenerđã được gắn với tên sự kiệneventNametương ứng.
Event & EventTarget & NodeEventTarget
Trước đây thì NodeJS chỉ có duy nhất EventEmitter để hỗ trợ lập trình hướng sự kiện. Tuy nhiên ở các phiên bản gần đây thì NodeJS đã triển khai thêm các class Event và EventTarget để mô phỏng lại phương thức quản lý sự kiện mà các trình duyệt web sử dụng.
Và hơn thế nữa thì các class tại các module cũng đã được áp dụng một giao diện mở rộng của EventTarget có tên là NodeEventTarget để tạo một phần giao diện lập trình. Do đó nên chúng ta sẽ có thể gặp một kiểu Event ở một class dựng sẵn nào đó mà khi listener được kích hoạt sẽ có một object mô tả event với rất nhiều thông tin chứ không chỉ có tên eventName.
Giao diện lập trình do NodeEventTarget cung cấp có các tên phương thức tương đồng với EventEmitter nhưng có số lượng phương thức ít hơn, và một số phương thức được mở rộng với các tham số tùy chọn options đóng gói trong một object.
Mặc dù ở thời điểm hiện tại thì NodeEventTarget vẫn chưa thể thay thế EventEmitter, tuy nhiên chúng ta vẫn cần tham khảo thông tin để có thể đọc và sử dụng code được dựng sẵn ở đâu đó.
Chẳng hạn nếu như nhìn vào tài liệu của một framework NodeJS nào đó mà có một câu lệnh kiểu như component.dispatch(event) thì chúng ta cũng cần biết là component được áp dụng NodeEventTarget; Và phương thức .dispatch() sẽ tự động kích hoạt các hàm xử lý sự kiện, đồng thời chuyển tiếp object event cho các listener.
- Tài liệu về class
Eventcủa NodeJS - Tài liệu về class
EventTargetcủa NodeJS - Tài liệu về class
NodeEventTargetcủa NodeJS
Kết thúc bài viết
Hiện tại thì chúng ta đã hoàn thành xong blog cá nhân đơn giản của Series này và đã chuyển tiếp sang Series App. Và bài viết này được thực hiện để chuẩn bị kiến thức cho tiến trình học tập của Series mới. Tuy nhiên nếu như bạn có ý tưởng nào đó với việc sử dụng Event trong code xử lý blog đã viết thì hãy mạnh dạn thử áp dụng nhé. Đặc biệt là ở những thao tác xử lý bất đồng bộ
async sẽ có thể viết lại code theo lối tư duy khác.
Trong bài viết tiếp theo, chúng ta sẽ tìm hiểu về cách tạo ra các tiến trình phụ trong môi trường NodeJS, giống như cách mà các thủ tục async được xử lý mặc định bởi môi trường. Đồng thời, điều này cũng sẽ mở ra một khả năng mới, giúp code của chúng ta có thêm cách tương tác với các phần mềm khác trong cùng thiết bị đang vận hành cả môi trường NodeJS.
(Sắp đăng tải) [NodeJS] Bài 9 – Process & Child
Nguồn: viblo.asia
