Thứ sáu, 29/05/2020 | 00:00 GMT+7

Sử dụng bộ phát sự kiện trong Node.js

Bộ phát sự kiện là các đối tượng trong Node.js kích hoạt một sự kiện bằng cách gửi một thông báo để báo hiệu rằng một hành động đã được hoàn thành. Các nhà phát triển JavaScript có thể viết mã lắng nghe các sự kiện từ trình phát sự kiện, cho phép họ thực thi các chức năng mỗi khi các sự kiện đó được kích hoạt. Trong bối cảnh này, các sự kiện bao gồm một chuỗi xác định và bất kỳ dữ liệu nào cần được chuyển cho người nghe.

Điển hình trong Node.js, khi ta muốn có một hành động xảy ra khi hoàn thành một hành động khác, ta sử dụng các kỹ thuật lập trình không đồng bộ như lồng các lệnh gọi lại hoặc chuỗi lời hứa. Tuy nhiên, những kỹ thuật này kết hợp chặt chẽ giữa hành động kích hoạt và hành động kết quả, gây khó khăn cho việc sửa đổi hành động kết quả trong tương lai. Trình phát sự kiện cung cấp một cách khác để cấu trúc mối quan hệ này: mô hình đăng ký xuất bản . Trong mẫu kiến trúc phần mềm này, một nhà xuất bản (người phát sự kiện) sẽ gửi một tin nhắn (một sự kiện), và một người đăng ký nhận sự kiện và thực hiện một hành động. Sức mạnh của mô hình này là nhà xuất bản không cần biết về người đăng ký. Một nhà xuất bản xuất bản một thông điệp và người đăng ký phản ứng với nó theo những cách tương ứng của họ. Nếu ta muốn thay đổi hành vi của ứng dụng của bạn , ta có thể điều chỉnh cách người đăng ký phản ứng với các sự kiện mà không cần phải thay đổi nhà xuất bản.

Trong bài viết này, ta sẽ tạo trình xử lý sự kiện cho lớp JavaScript TicketManager cho phép user mua vé. Ta sẽ cài đặt trình lắng nghe cho sự kiện buy , sự kiện này sẽ kích hoạt mỗi khi mua vé. Quá trình này cũng sẽ chỉ ra cách quản lý các sự kiện bị lỗi từ bộ phát và cách quản lý người đăng ký sự kiện.

Yêu cầu

Bước 1 - Phát ra sự kiện

Trong bước này, ta sẽ khám phá hai cách phổ biến nhất để tạo trình phát sự kiện trong Node.js. Đầu tiên là sử dụng trực tiếp một đối tượng phát sự kiện và thứ hai là tạo một đối tượng mở rộng đối tượng phát sự kiện.

Quyết định sử dụng cái nào phụ thuộc vào mức độ kết hợp các sự kiện của bạn với các hành động của đối tượng. Nếu các sự kiện bạn muốn phát ra là hiệu ứng của các hành động của một đối tượng, bạn có thể mở rộng từ đối tượng phát sự kiện để có quyền truy cập vào các chức năng của nó một cách thuận tiện. Nếu các sự kiện bạn muốn phát độc lập với các đối tượng nghiệp vụ của bạn hoặc là kết quả của các hành động từ nhiều đối tượng nghiệp vụ, thay vào đó, bạn sẽ tạo một đối tượng phát sự kiện độc lập được các đối tượng của bạn tham chiếu.

Hãy bắt đầu bằng cách tạo một đối tượng độc lập, phát ra sự kiện. Ta sẽ bắt đầu bằng cách tạo một folder để lưu trữ tất cả mã của ta . Trong terminal của bạn, hãy tạo một folder mới có tên là event-emitters :

  • mkdir event-emitters

Sau đó nhập folder đó:

  • cd event-emitters

Mở trình phát sự kiện đầu tiên, firstEventEmitter.js , trong editor . Ta sẽ sử dụng nano vì nó có sẵn trong terminal :

  • nano firstEventEmitter.js

Trong Node.js, ta phát ra các sự kiện thông qua lớp EventEmitter . Lớp này là một phần của module events . Trước tiên, hãy bắt đầu bằng cách tải module events trong file của ta bằng cách thêm dòng sau:

event-releaseters / firstEventEmitter.js
const { EventEmitter } = require("events"); 

Với lớp được nhập, ta có thể sử dụng nó để tạo một thể hiện đối tượng mới từ nó:

event-releaseters / firstEventEmitter.js
const { EventEmitter } = require("events");  const firstEmitter = new EventEmitter(); 

Hãy tạo một sự kiện bằng cách thêm dòng được đánh dấu sau vào cuối firstEventEmitter.js :

event-releaseters / firstEventEmitter.js
const { EventEmitter } = require("events");  const firstEmitter = new EventEmitter();   firstEmitter.emit("My first event"); 

Hàm emit() được sử dụng để kích hoạt các sự kiện. Ta cần chuyển tên của sự kiện cho nó dưới dạng một chuỗi. Ta có thể thêm bất kỳ số lượng đối số nào sau tên sự kiện. Các sự kiện chỉ có một cái tên là khá hạn chế; các đối số khác cho phép ta gửi dữ liệu đến người nghe của ta . Khi ta cài đặt người quản lý vé, các sự kiện của ta sẽ chuyển dữ liệu về giao dịch mua khi nó xảy ra. Hãy ghi nhớ tên của sự kiện, vì những người nghe sự kiện sẽ xác định nó bằng tên này.

Lưu ý: Mặc dù ta không nắm bắt nó trong ví dụ này, nhưng hàm emit() trả về true nếu có người nghe cho sự kiện. Nếu không có người nghe cho một sự kiện, nó sẽ trả về false .

Hãy chạy file này để xem điều gì sẽ xảy ra. Lưu và thoát nano , sau đó thực thi file bằng lệnh node :

  • node firstEventEmitter.js

Khi tập lệnh kết thúc quá trình thực thi, bạn sẽ không thấy kết quả nào trong terminal . Đó là bởi vì ta không đăng nhập bất kỳ thư nào trong firstEventEmitter.js và không có gì lắng nghe sự kiện đã được gửi. Sự kiện được phát ra, nhưng không có gì tác động lên những sự kiện này.

Hãy cùng hướng tới việc xem một ví dụ đầy đủ hơn về xuất bản, lắng nghe và hành động theo các sự kiện. Ta sẽ thực hiện việc này bằng cách tạo một ứng dụng ví dụ về trình quản lý vé. Người quản lý vé sẽ hiển thị một chức năng để mua vé. Khi một vé được mua, một sự kiện sẽ được gửi với thông tin chi tiết của người mua. Sau đó, ta sẽ tạo một module Node.js khác để mô phỏng một email được gửi tới email của người mua xác nhận việc mua hàng.

Hãy bắt đầu bằng cách tạo trình quản lý vé của ta . Nó sẽ mở rộng lớp EventEmitter để ta không phải tạo một đối tượng phát sự kiện riêng để phát ra các sự kiện.

Trong cùng một folder làm việc, hãy tạo và mở một file mới có tên là ticketManager.js :

  • nano ticketManager.js

Như với trình phát sự kiện đầu tiên, ta cần nhập lớp EventEmitter từ module events . Đặt mã sau ở đầu file :

sự kiện-phát / véManager.js
const EventEmitter = require("events"); 

Bây giờ, hãy tạo một lớp TicketManager mới sẽ sớm xác định phương thức mua vé:

sự kiện-phát / véManager.js
const EventEmitter = require("events");  class TicketManager extends EventEmitter {} 

Trong trường hợp này, lớp TicketManager mở rộng lớp EventEmitter . Điều này nghĩa là lớp TicketManager kế thừa các phương thức và thuộc tính của lớp EventEmitter . Đây là cách nó truy cập vào phương thức emit() .

Trong trình quản lý vé của ta , ta muốn cung cấp nguồn vé ban đầu có thể được mua. Ta sẽ làm điều này bằng cách chấp nhận cung cấp ban đầu trong hàm constructor() của ta , một hàm đặc biệt được gọi khi một đối tượng mới của một lớp được tạo. Thêm hàm tạo sau vào lớp TicketManager :

sự kiện-phát / véManager.js
const EventEmitter = require("events");  class TicketManager extends EventEmitter {     constructor(supply) {         super();         this.supply = supply;     } } 

Hàm tạo có một đối số supply . Đây là con số chi tiết về nguồn cung cấp vé ban đầu mà ta có thể bán. Mặc dù ta đã khai báo rằng TicketManager là một lớp con của EventEmitter , ta vẫn cần gọi super() để có quyền truy cập vào các phương thức và thuộc tính của EventEmitter . Hàm super() gọi hàm khởi tạo của hàm mẹ, trong trường hợp này là EventEmitter .

Cuối cùng, ta tạo một thuộc tính supply cho đối tượng với this.supply và cung cấp cho nó giá trị được phương thức khởi tạo truyền vào.

Bây giờ, hãy thêm một phương thức buy() sẽ được gọi khi mua vé. Phương pháp này sẽ giảm nguồn cung cấp một và tạo ra một sự kiện với dữ liệu mua hàng.

Thêm phương thức buy() như sau:

sự kiện-phát / véManager.js
const EventEmitter = require("events");  class TicketManager extends EventEmitter {     constructor(supply) {         super();         this.supply = supply;     }      buy(email, price) {         this.supply--;         this.emit("buy", email, price, Date.now());     } } 

Trong hàm buy() , ta lấy địa chỉ email của người mua và giá họ đã trả cho vé. Sau đó, ta giảm lượng vé cung cấp đi một. Ta kết thúc bằng cách tạo ra một sự kiện buy . Lần này, ta tạo ra một sự kiện với dữ liệu bổ sung: email và giá đã được chuyển trong hàm cũng như dấu thời gian về thời điểm thực hiện mua hàng.

Để các mô-đun Node.js khác của ta có thể sử dụng lớp này, ta cần xuất nó. Thêm dòng này vào cuối file :

sự kiện-phát / véManager.js
...  module.exports = TicketManager 

Lưu và thoát khỏi file .

Ta đã hoàn tất việc cài đặt trình phát sự kiện TicketManager . Bây giờ ta đã đặt mọi thứ để đẩy các sự kiện, ta có thể chuyển sang đọc và xử lý các sự kiện đó. Để làm điều đó, ta sẽ tạo trình nghe sự kiện trong bước tiếp theo.

Bước 2 - Lắng nghe sự kiện

Node.js cho phép ta thêm trình lắng nghe cho một sự kiện với hàm on() của đối tượng phát sự kiện. Điều này sẽ lắng nghe một tên sự kiện cụ thể và kích hoạt một lệnh gọi lại khi sự kiện được kích hoạt. Việc thêm người nghe thường trông giống như sau:

eventEmitter.on(event_name, callback_function) {     action } 

Lưu ý:: Node.js là alias của phương thức on() với addListener() . Chúng thực hiện cùng một nhiệm vụ. Trong hướng dẫn này, ta sẽ tiếp tục sử dụng on() .

Hãy cùng trải nghiệm trực tiếp với việc lắng nghe sự kiện đầu tiên của ta . Tạo một file mới có tên firstListener.js :

  • nano firstListener.js

Để minh họa cho cách hoạt động của hàm on() , hãy ghi lại một thông báo đơn giản khi nhận được sự kiện đầu tiên của ta .

Đầu tiên, hãy nhập TicketManager vào module Node.js mới của ta . Thêm mã sau vào firstListener.js :

event-releaseters / firstListener.js
const TicketManager = require("./ticketManager");  const ticketManager = new TicketManager(10); 

Nhớ lại rằng các đối tượng TicketManager cần nguồn cung cấp vé ban đầu khi được tạo. Đây là lý do tại sao ta vượt qua đối số 10 .

Bây giờ, hãy thêm trình nghe sự kiện Node.js đầu tiên của ta . Nó sẽ lắng nghe sự kiện buy . Thêm mã được đánh dấu sau:

event-releaseters / firstListener.js
const TicketManager = require("./ticketManager");  const ticketManager = new TicketManager(10);  ticketManager.on("buy", () => {     console.log("Someone bought a ticket!"); }); 

Để thêm trình nghe mới, ta đã sử dụng hàm on() , một phần của đối tượng ticketManager . Phương thức on() khả dụng cho tất cả các đối tượng bộ phát sự kiện và vì TicketManager kế thừa từ lớp EventEmitter , phương thức này có sẵn trên tất cả các đối tượng cá thể TicketManager .

Đối số thứ hai của phương thức on() là một hàm gọi lại, được viết dưới dạng một hàm mũi tên . Mã trong hàm này được chạy sau khi sự kiện được phát ra. Trong trường hợp này, ta ghi lại "Someone bought a ticket!" vào console nếu sự kiện buy được phát ra.

Bây giờ ta đã cài đặt trình lắng nghe, hãy sử dụng hàm buy() để sự kiện sẽ được phát ra. Ở cuối file của bạn, hãy thêm cái này:

event-releaseters / firstListener.js
...  ticketManager.buy("test@email.com", 20); 

Thao tác này thực hiện phương thức buy với email user là test@email.com và giá vé là 20 .

Lưu và thoát khỏi file .

Bây giờ hãy chạy tập lệnh này với node :

  • node firstListener.js

Control panel của bạn sẽ hiển thị:

Output
Someone bought a ticket!

Trình nghe sự kiện đầu tiên của bạn đã hoạt động. Hãy xem điều gì sẽ xảy ra nếu ta mua nhiều vé. Mở lại firstListener.js của bạn trong editor của bạn:

  • nano firstListener.js

Ở cuối file , hãy thực hiện một lệnh gọi khác đến hàm buy() :

event-releaseters / firstListener.js
...  ticketManager.buy("test@email.com", 20); ticketManager.buy("test@email.com", 20); 

Lưu và thoát khỏi file . Hãy chạy script với Node.js để xem điều gì sẽ xảy ra:

  • node firstListener.js

Control panel của bạn sẽ hiển thị:

Output
Someone bought a ticket! Someone bought a ticket!

Vì hàm buy() được gọi hai lần, hai sự kiện buy đã được phát ra. Người nghe của ta đã chọn cả hai.

Đôi khi ta chỉ quan tâm đến việc lắng nghe lần đầu tiên một sự kiện được kích hoạt, trái ngược với tất cả những lần nó được phát ra. Node.js cung cấp một giải pháp thay thế on() cho trường hợp này bằng hàm once() .

Giống như on() , hàm once() chấp nhận tên sự kiện làm đối số đầu tiên của nó và một hàm gọi lại được gọi khi sự kiện được kích hoạt làm đối số thứ hai của nó. Bên dưới, khi sự kiện được phát ra và nhận bởi một trình nghe sử dụng once() , Node.js sẽ tự động loại bỏ trình nghe đó và sau đó thực thi mã trong hàm gọi lại.

Hãy xem once() hoạt động bằng cách chỉnh sửa firstListener.js :

  • nano firstListener.js

Ở cuối file , hãy thêm trình xử lý sự kiện mới bằng cách sử dụng once() như các dòng được đánh dấu sau:

event-releaseters / firstListener.js
const TicketManager = require("./ticketManager");  const ticketManager = new TicketManager(10);  ticketManager.on("buy", () => {         console.log("Someone bought a ticket!"); });  ticketManager.buy("test@email.com", 20); ticketManager.buy("test@email.com", 20);  ticketManager.once("buy", () => {     console.log("This is only called once"); }); 

Lưu và thoát khỏi file và chạy chương trình này với node :

  • node firstListener.js

Đầu ra giống như lần trước:

Output
Someone bought a ticket! Someone bought a ticket!

Mặc dù ta đã thêm trình xử lý sự kiện mới với once() , nhưng nó đã được thêm vào sau khi các sự kiện buy được phát ra. Do đó, người nghe đã không phát hiện ra hai sự kiện này. Bạn không thể lắng nghe các sự kiện đã xảy ra trong quá khứ. Khi bạn thêm người nghe, bạn chỉ có thể nắm bắt các sự kiện xảy ra sau đó.

Hãy thêm một vài lệnh gọi hàm buy() nữa để ta có thể xác nhận trình nghe once() chỉ phản ứng một lần. Mở firstListener.js trong editor của bạn như trước:

  • nano firstListener.js

Thêm các lệnh gọi sau vào cuối file :

event-releaseters / firstListener.js
...  ticketManager.once("buy", () => {     console.log("This is only called once"); });  ticketManager.buy("test@email.com", 20); ticketManager.buy("test@email.com", 20); 

Lưu và thoát, sau đó thực hiện chương trình này:

  • node firstListener.js

Đầu ra của bạn sẽ là:

Output
Someone bought a ticket! Someone bought a ticket! Someone bought a ticket! This is only called once Someone bought a ticket!

Hai dòng đầu tiên là từ hai cuộc gọi buy() đầu tiên trước khi người nghe once() được thêm vào. Việc thêm trình xử lý sự kiện mới không xóa những trình xử lý sự kiện trước đó, vì vậy trình xử lý sự kiện đầu tiên mà ta đã thêm vẫn hoạt động và ghi log thông báo.

Vì trình xử lý sự kiện với on() được khai báo trước trình xử lý sự kiện với once() , ta thấy Someone bought a ticket! trước This is only called once . Cả hai dòng này đều phản ứng với sự kiện buy lần thứ hai đến lần buy cuối cùng.

Cuối cùng, khi lệnh gọi buy() cuối cùng được thực hiện, bộ phát sự kiện chỉ có người nghe đầu tiên được tạo với on() . Như đã đề cập trước đó, khi một bộ xử lý sự kiện được tạo bằng once() nhận một sự kiện, nó sẽ tự động bị loại bỏ.

Bây giờ ta đã thêm trình nghe sự kiện để phát hiện trình phát của ta , ta sẽ xem cách thu thập dữ liệu với những trình nghe đó.

Bước 3 - Thu thập dữ liệu sự kiện

Lúc này, bạn đã cài đặt trình nghe sự kiện để phản ứng với các sự kiện được phát ra. Các sự kiện được phát ra cũng truyền theo dữ liệu. Hãy xem cách ta có thể nắm bắt dữ liệu đi kèm với một sự kiện.

Ta sẽ bắt đầu bằng cách tạo một số module Node.js mới: dịch vụ email và dịch vụ database . Chúng sẽ được sử dụng để mô phỏng việc gửi email và lưu vào database tương ứng. Sau đó, ta sẽ liên kết tất cả chúng lại với nhau bằng tập lệnh Node.js chính của ta — index.js .

Hãy bắt đầu bằng cách chỉnh sửa module dịch vụ email của ta . Mở file trong editor của bạn:

  • nano emailService.js

Dịch vụ email của ta bao gồm một lớp chứa một phương thức— send() . Phương pháp này mong đợi email được gửi cùng với các sự kiện buy . Thêm mã sau vào file của bạn:

event-releaseters / emailService.js
class EmailService {     send(email) {         console.log(`Sending email to ${email}`);     } }  module.exports = EmailService 

Mã này tạo một lớp EmailService có chứa hàm send() . Thay vì gửi một email thực, nó sử dụng các ký tự mẫu để ghi một thông báo vào console có chứa địa chỉ email của ai đó mua vé. Lưu và thoát trước khi tiếp tục.

Hãy cài đặt dịch vụ database . Mở databaseService.js bằng editor của bạn:

  • nano databaseService.js

Dịch vụ database lưu dữ liệu mua hàng của ta vào database thông qua phương thức save() của nó. Thêm mã sau vào databaseService.js :

event-releaseters / databaseService.js
class DatabaseService {     save(email, price, timestamp) {         console.log(`Running query: INSERT INTO orders VALUES (email, price, created) VALUES (${email}, ${price}, ${timestamp})`);     } }  module.exports = DatabaseService 

Điều này tạo ra một lớp DatabaseService mới có chứa một phương thức save() . Tương tự như phương thức send() của dịch vụ email, hàm save() sử dụng dữ liệu đi kèm với sự kiện buy , ghi nó vào console thay vì thực sự chèn nó vào database . Phương thức này cần email của người mua, giá vé và thời gian vé được mua để hoạt động. Lưu và thoát khỏi file .

Ta sẽ sử dụng file cuối cùng của bạn để kết hợp TicketManager , EmailServiceDatabaseService lại với nhau. Nó sẽ cài đặt một bộ lắng nghe cho sự kiện buy và sẽ gọi hàm send() của dịch vụ email và hàm save() của dịch vụ database .

Mở index.js trong editor của bạn:

  • nano index.js

Điều đầu tiên cần làm là nhập các module ta đang sử dụng:

sự kiện-phát / index.js
const TicketManager = require("./ticketManager"); const EmailService = require("./emailService"); const DatabaseService = require("./databaseService"); 

Tiếp theo, hãy tạo các đối tượng cho các lớp mà ta đã nhập. Ta sẽ đặt một lượng vé thấp là ba cho cuộc trình diễn này:

sự kiện-phát / index.js
const TicketManager = require("./ticketManager"); const EmailService = require("./emailService"); const DatabaseService = require("./databaseService");  const ticketManager = new TicketManager(3); const emailService = new EmailService(); const databaseService = new DatabaseService(); 

Bây giờ ta có thể cài đặt trình lắng nghe của bạn với các đối tượng khởi tạo. Khi nào ai đó mua vé, ta muốn gửi email cho họ cũng như lưu dữ liệu vào database . Thêm trình nghe sau vào mã của bạn:

sự kiện-phát / index.js
const TicketManager = require("./ticketManager"); const EmailService = require("./emailService"); const DatabaseService = require("./databaseService");  const ticketManager = new TicketManager(3); const emailService = new EmailService(); const databaseService = new DatabaseService();   ticketManager.on("buy", (email, price, timestamp) => {     emailService.send(email);     databaseService.save(email, price, timestamp); }); 

Giống như trước đây, ta thêm một trình lắng nghe với phương thức on() . Sự khác biệt lần này là ta có ba đối số trong hàm gọi lại của ta . Mỗi đối số tương ứng với dữ liệu mà sự kiện phát ra. Xin nhắc lại, đây là mã phát trong hàm buy() :

sự kiện-phát / véManager.js
this.emit("buy", email, price, Date.now()); 

Trong lần gọi lại của ta , trước tiên ta ghi lại email từ bộ phát, sau đó là price và cuối cùng là dữ liệu Date.now() , ta ghi lại dưới dạng timestamp .

Khi trình nghe của ta phát hiện một sự kiện buy , nó sẽ gọi hàm send() từ đối tượng emailService cũng như hàm save() từ databaseService . Để kiểm tra xem cài đặt này có hoạt động hay không, hãy gọi hàm buy() ở cuối file :

sự kiện-phát / index.js
...  ticketManager.buy("test@email.com", 10); 

Lưu và thoát khỏi editor . Bây giờ, hãy chạy tập lệnh này với node và quan sát những gì tiếp theo. Trong terminal của bạn, hãy nhập:

  • node index.js

Bạn sẽ thấy kết quả sau:

Output
Sending email to test@email.com Running query: INSERT INTO orders VALUES (email, price, created) VALUES (test@email.com, 10, 1588720081832)

Dữ liệu đã được thu thập và trả về thành công trong chức năng gọi lại của ta . Với kiến thức này, bạn có thể cài đặt bộ lắng nghe cho nhiều bộ phát với tên sự kiện và dữ liệu khác nhau. Tuy nhiên, có những sắc thái nhất định để xử lý các sự kiện lỗi với bộ phát sự kiện.

Tiếp theo, hãy xem cách xử lý các sự kiện lỗi và những tiêu chuẩn nào ta nên tuân theo khi làm như vậy.

Bước 4 - Xử lý sự kiện lỗi

Nếu một bộ phát sự kiện không thể thực hiện hành động của bạn , nó sẽ phát ra một sự kiện để báo hiệu rằng hành động không thành công. Trong Node.js, cách tiêu chuẩn để bộ phát sự kiện báo hiệu lỗi là phát ra một sự kiện lỗi .

Sự kiện lỗi phải có tên của nó được đặt thành error . Nó cũng phải được đi kèm với một đối tượng Error . Hãy xem một ví dụ thực tế về việc tạo ra một sự kiện lỗi.

Người quản lý vé của ta giảm nguồn cung cấp một lần mỗi khi hàm buy() được gọi. Hiện tại, không có gì ngăn cản nó bán được nhiều vé hơn số lượng có sẵn. Hãy sửa đổi hàm buy() để nếu nguồn cung vé về 0 và ai đó muốn mua vé, ta sẽ phát ra lỗi cho biết rằng ta đã hết hàng.

Mở ticketManager.js trong editor của bạn :

  • nano ticketManager.js

Bây giờ hãy chỉnh sửa hàm buy() như sau:

sự kiện-phát / véManager.js
...  buy(email, price) {     if (this.supply > 0) {         this.supply—;         this.emit("buy", email, price, Date.now());         return;     }      this.emit("error", new Error("There are no more tickets left to purchase")); } ... 

Ta đã thêm một if tuyên bố cho phép mua vé nếu nguồn cung cấp của ta là lớn hơn không. Nếu ta không có bất kỳ vé nào khác, ta sẽ tạo ra một sự kiện error . Sự kiện error được tạo ra với một đối tượng Error mới chứa mô tả lý do tại sao ta gặp lỗi này.

Lưu và thoát khỏi file . Hãy thử xử lý lỗi này trong index.js của ta . Ngay bây giờ, ta chỉ mua một vé. Ta đã khởi tạo đối tượng ticketManager với ba vé, vì vậy ta sẽ gặp lỗi nếu ta cố gắng mua bốn vé.

Chỉnh sửa index.js bằng editor của bạn:

  • nano index.js

Bây giờ, hãy thêm các dòng sau vào cuối file để ta có thể mua tổng cộng bốn vé:

sự kiện-phát / index.js
...  ticketManager.buy("test@email.com", 10); ticketManager.buy("test@email.com", 10); ticketManager.buy("test@email.com", 10); ticketManager.buy("test@email.com", 10); 

Lưu và thoát khỏi editor .

Hãy thực thi file này để xem điều gì sẽ xảy ra:

  • node index.js

Đầu ra của bạn sẽ là:

Output
Sending email to test@email.com Running query: INSERT INTO orders VALUES (email, price, created) VALUES (test@email.com, 10, 1588724932796) Sending email to test@email.com Running query: INSERT INTO orders VALUES (email, price, created) VALUES (test@email.com, 10, 1588724932812) Sending email to test@email.com Running query: INSERT INTO orders VALUES (email, price, created) VALUES (test@email.com, 10, 1588724932812) events.js:196 throw er; // Unhandled 'error' event ^ Error: There are no more tickets left to purchase at TicketManager.buy (/home/sammy/event-emitters/ticketManager.js:16:28) at Object.<anonymous> (/home/sammy/event-emitters/index.js:17:15) at Module._compile (internal/modules/cjs/loader.js:1128:30) at Object.Module._extensions..js (internal/modules/cjs/loader.js:1167:10) at Module.load (internal/modules/cjs/loader.js:983:32) at Function.Module._load (internal/modules/cjs/loader.js:891:14) at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12) at internal/main/run_main_module.js:17:47 Emitted 'error' event on TicketManager instance at: at TicketManager.buy (/home/sammy/event-emitters/ticketManager.js:16:14) at Object.<anonymous> (/home/sammy/event-emitters/index.js:17:15) [... lines matching original stack trace ...] at internal/main/run_main_module.js:17:47

Ba sự kiện buy đầu tiên đã được xử lý chính xác, nhưng đến sự kiện buy thứ tư, chương trình của ta đã bị lỗi. Hãy kiểm tra phần đầu của thông báo lỗi:

Output
... events.js:196 throw er; // Unhandled 'error' event ^ Error: There are no more tickets left to purchase at TicketManager.buy (/home/sammy/event-emitters/ticketManager.js:16:28) ...

Hai dòng đầu tiên đánh dấu rằng một lỗi đã được tạo ra. Comment cho biết "Unhandled 'error' event" . Nếu một trình phát sự kiện phát ra lỗi và ta không đính kèm trình lắng nghe cho các sự kiện lỗi, Node.js sẽ ném lỗi và làm hỏng chương trình.

Phương pháp hay nhất được coi là luôn lắng nghe các sự kiện error nếu bạn đang nghe bộ phát sự kiện. Nếu bạn không cài đặt trình lắng nghe lỗi, toàn bộ ứng dụng của bạn sẽ bị treo nếu một lỗi được phát ra. Với trình nghe lỗi, bạn có thể xử lý nó một cách duyên dáng.

Để làm theo các phương pháp hay nhất, hãy cài đặt bộ lắng nghe lỗi. Mở lại index.js :

  • nano index.js

Thêm người nghe trước khi ta bắt đầu mua vé. Lưu ý người nghe chỉ có thể phản hồi các sự kiện được phát ra sau khi nó được thêm vào. Chèn một trình nghe lỗi như thế này:

sự kiện-phát / index.js
...  ticketManager.on("error", (error) => {     console.error(`Gracefully handling our error: ${error}`); });  ticketManager.buy("test@email.com", 10); ticketManager.buy("test@email.com", 10); ticketManager.buy("test@email.com", 10); ticketManager.buy("test@email.com", 10); 

Khi ta nhận được một sự kiện lỗi, ta sẽ ghi nó vào console bằng console.error() .

Lưu và để lại nano . Chạy lại tập lệnh để xem sự kiện lỗi của ta được xử lý chính xác:

  • node index.js

Lần này kết quả sau sẽ được hiển thị:

Output
Sending email to test@email.com Running query: INSERT INTO orders VALUES (email, price, created) VALUES (test@email.com, 10, 1588726293332) Sending email to test@email.com Running query: INSERT INTO orders VALUES (email, price, created) VALUES (test@email.com, 10, 1588726293348) Sending email to test@email.com Running query: INSERT INTO orders VALUES (email, price, created) VALUES (test@email.com, 10, 1588726293348) Gracefully handling our error: Error: There are no more tickets left to purchase

Từ dòng cuối cùng, ta xác nhận sự kiện lỗi của ta đang được xử lý bởi trình nghe thứ hai của ta và quy trình Node.js không bị lỗi.

Bây giờ ta đã đề cập đến các khái niệm về gửi và lắng nghe sự kiện, hãy xem xét một số chức năng bổ sung được dùng để quản lý trình lắng nghe sự kiện.

Bước 5 - Quản lý người nghe sự kiện

Bộ phát sự kiện đi kèm với một số cơ chế để theo dõi và kiểm soát số lượng người nghe đã đăng ký một sự kiện. Để biết tổng quan về số lượng người nghe đang xử lý một sự kiện, ta có thể sử dụng phương thức listenerCount() bao gồm trong mọi đối tượng.

Phương thức listenerCount() chấp nhận một đối số: tên sự kiện mà bạn muốn đếm. Hãy xem nó hoạt động trong index.js .

Mở file bằng nano hoặc editor mà bạn chọn:

  • nano index.js

Bạn hiện đang gọi hàm buy() bốn lần. Bỏ bốn dòng đó. Khi bạn thực hiện, hãy thêm hai dòng mới này để toàn bộ index.js của bạn trông giống như sau:

sự kiện-phát / index.js
const TicketManager = require("./ticketManager"); const EmailService = require("./emailService"); const DatabaseService = require("./databaseService");  const ticketManager = new TicketManager(3); const emailService = new EmailService(); const databaseService = new DatabaseService();  ticketManager.on("buy", (email, price, timestamp) => {     emailService.send(email);     databaseService.save(email, price, timestamp); });  ticketManager.on("error", (error) => {     console.error(`Gracefully handling our error: ${error}`); });  console.log(`We have ${ticketManager.listenerCount("buy")} listener(s) for the buy event`); console.log(`We have ${ticketManager.listenerCount("error")} listener(s) for the error event`); 

Ta đã loại bỏ các lệnh gọi buy() từ phần trước và thay vào đó, ghi hai dòng vào console . Câu lệnh log đầu tiên sử dụng hàm listenerCount() để hiển thị số lượng người nghe cho sự kiện buy() . Câu lệnh log thứ hai cho biết ta có bao nhiêu người nghe đối với sự kiện error .

Lưu và thoát. Bây giờ hãy chạy tập lệnh của bạn bằng lệnh node :

  • node index.js

Bạn sẽ nhận được kết quả này:

Output
We have 1 listener(s) for the buy event We have 1 listener(s) for the error event

Ta chỉ sử dụng hàm on() một lần cho sự kiện buy và một lần cho sự kiện error , vì vậy kết quả này phù hợp với mong đợi của ta .

Tiếp theo, ta sẽ sử dụng listenerCount() khi ta xóa người nghe khỏi trình phát sự kiện. Ta có thể cần xóa người nghe sự kiện khi khoảng thời gian của sự kiện không còn áp dụng. Ví dụ: nếu người quản lý vé của ta được sử dụng cho một buổi hòa nhạc cụ thể, khi buổi hòa nhạc kết thúc, bạn sẽ xóa những người nghe sự kiện.

Trong Node.js, ta sử dụng hàm off() để loại bỏ trình xử lý sự kiện khỏi trình phát sự kiện. Phương thức off() chấp nhận hai đối số: tên sự kiện và hàm đang lắng nghe nó.

Lưu ý : Tương tự như hàm on() , Node.js đặt alias cho phương thức off() với removeListener() . Cả hai đều làm điều tương tự, với cùng lý lẽ. Trong hướng dẫn này, ta sẽ tiếp tục sử dụng off() .

Đối với đối số thứ hai của hàm off() , ta cần tham chiếu đến lệnh gọi lại đang lắng nghe một sự kiện. Do đó, để loại bỏ một trình nghe sự kiện, lệnh gọi lại của nó phải được lưu vào một số biến hoặc hằng số. Như vậy, ta không thể loại bỏ trình lắng nghe sự kiện hiện tại do buy hoặc error với hàm off() .

Để off() hoạt động, hãy thêm trình xử lý sự kiện mới mà ta sẽ xóa trong các lần gọi tiếp theo. Đầu tiên, hãy xác định callback trong một biến để ta có thể tham chiếu nó trong off() sau này. Mở index.js bằng nano :

  • nano index.js

Ở cuối index.js thêm vào:

sự kiện-phát / index.js
...  const onBuy = () => {     console.log("I will be removed soon"); }; 

Bây giờ, hãy thêm trình nghe sự kiện khác cho sự kiện buy :

sự kiện-phát / index.js
...  ticketManager.on("buy", onBuy); 

Để chắc chắn rằng ta đã thêm thành công bộ lắng nghe sự kiện đó, hãy in số bộ nghe cho buy và gọi hàm buy() .

sự kiện-phát / index.js
...  console.log(`We added a new event listener bringing our total count for the buy event to: ${ticketManager.listenerCount("buy")}`); ticketManager.buy("test@email", 20); 

Lưu và thoát khỏi file , sau đó chạy chương trình:

  • node index.js

Thông báo sau sẽ được hiển thị trong terminal :

Output
We have 1 listener(s) for the buy event We have 1 listener(s) for the error event We added a new event listener bringing our total count for the buy event to: 2 Sending email to test@email Running query: INSERT INTO orders VALUES (email, price, created) VALUES (test@email, 20, 1588814306693) I will be removed soon

Từ kết quả , ta thấy câu lệnh log của ta từ khi ta thêm trình nghe sự kiện mới. Sau đó, ta gọi hàm buy() và cả hai người nghe đều phản ứng với nó. Người nghe đầu tiên đã gửi email và dữ liệu đã lưu vào database , sau đó người nghe thứ hai của ta in thông báo của nó I will be removed soon màn hình.

Bây giờ hãy sử dụng hàm off() để loại bỏ trình nghe sự kiện thứ hai. Mở lại file trong nano :

  • nano index.js

Bây giờ, hãy thêm lệnh gọi off() vào cuối file . Bạn cũng sẽ thêm một báo cáo log để xác nhận số lượng người nghe và thực hiện một cuộc gọi buy() :

sự kiện-phát / index.js
...  ticketManager.off("buy", onBuy);  console.log(`We now have: ${ticketManager.listenerCount("buy")} listener(s) for the buy event`); ticketManager.buy("test@email", 20); 

Lưu ý cách biến onBuy được sử dụng làm đối số thứ hai của off() . Lưu và thoát khỏi file .

Bây giờ chạy điều này với node :

  • node index.js

Đầu ra trước đó sẽ không thay đổi, nhưng lần này ta sẽ tìm thấy dòng log mới mà ta đã thêm xác nhận ta có một người nghe . Khi buy() được gọi lại, ta sẽ chỉ thấy kết quả của lệnh gọi lại được người nghe đầu tiên sử dụng:

Output
We have 1 listener(s) for the buy event We have 1 listener(s) for the error event We added a new event listener bringing our total count for the buy event to: 2 Sending email to test@email Running query: INSERT INTO orders VALUES (email, price, created) VALUES (test@email, 20, 1588816352178) I will be removed soon We now have: 1 listener(s) for the buy event Sending email to test@email Running query: INSERT INTO orders VALUES (email, price, created) VALUES (test@email, 20, 1588816352178)

Nếu ta muốn loại bỏ tất cả các sự kiện với off() , ta có thể sử dụng hàm removeAllListeners() . Hàm này chấp nhận một đối số: tên của sự kiện mà ta muốn xóa người nghe.

Hãy sử dụng chức năng này ở cuối file để gỡ bỏ trình nghe sự kiện đầu tiên mà ta đã thêm cho sự kiện buy . Mở index.js :

  • nano index.js

Trước tiên, bạn sẽ xóa tất cả các trình nghe bằng removeAllListeners() . Sau đó, bạn sẽ ghi lại một câu lệnh với số lượng người nghe bằng cách sử dụng hàm listenerCount() . Để xác nhận nó không còn nghe nữa, bạn sẽ mua một vé khác. Khi sự kiện được phát ra, không có gì sẽ phản ứng với nó.

Nhập mã sau vào cuối file :

sự kiện-phát / index.js
...  ticketManager.removeAllListeners("buy"); console.log(`We have ${ticketManager.listenerCount("buy")} listeners for the buy event`); ticketManager.buy("test@email", 20); console.log("The last ticket was bought"); 

Lưu và thoát khỏi file .

Bây giờ, hãy thực thi mã của ta bằng lệnh node :

  • node index.js
We have 1 listener(s) for the buy event We have 1 listener(s) for the error event We added a new event listener bringing our total count for the buy event to: 2 Sending email to test@email Running query: INSERT INTO orders VALUES (email, price, created) VALUES (test@email, 20, 1588817058088) I will be removed soon We now have: 1 listener(s) for the buy event Sending email to test@email Running query: INSERT INTO orders VALUES (email, price, created) VALUES (test@email, 20, 1588817058088) We have 0 listeners for the buy event The last ticket was bought 

Sau khi xóa tất cả người nghe, ta không còn gửi email hoặc lưu vào database để mua vé.

Kết luận

Trong hướng dẫn này, bạn đã học cách sử dụng bộ phát sự kiện Node.js để kích hoạt sự kiện. Bạn đã phát ra các sự kiện bằng hàm EventEmitter emit() của một đối tượng EventEmitter , sau đó lắng nghe các sự kiện với hàm on()once() để thực thi mã mỗi khi sự kiện được kích hoạt. Bạn cũng đã thêm một trình nghe cho một sự kiện lỗi và giám sát và quản lý các trình nghe bằng hàm listenerCount() .

Với các cuộc gọi lại và lời hứa, hệ thống quản lý vé của ta cần được tích hợp với các module dịch vụ database và email để có được chức năng tương tự. Vì ta sử dụng bộ phát sự kiện, sự kiện đã được tách riêng khỏi các triển khai. Hơn nữa, bất kỳ module nào có quyền truy cập vào trình quản lý vé đều có thể quan sát sự kiện của nó và phản ứng với nó. Nếu bạn muốn các module Node.js, nội bộ hoặc bên ngoài, quan sát những gì đang xảy ra với đối tượng của bạn , hãy cân nhắc đặt nó thành bộ phát sự kiện để có khả năng mở rộng.

Để tìm hiểu thêm về các sự kiện trong Node.js, bạn có thể đọc tài liệu Node.js. Nếu bạn muốn tiếp tục học Node.js, bạn có thể quay lại loạt bài Cách viết mã trong Node.js hoặc duyệt qua các dự án lập trình và cài đặt trên trang chủ đề Node của ta .


Tags:

Các tin liên quan