Bài 7: NodeJS – Sự kiện Emitter – NodeJS căn bản cho người mới bắt đầu

Bài 7: NodeJS – Sự kiện Emitter                        – NodeJS căn bản cho người mới bắt đầu

Bài 7: NodeJS – Sự kiện Emitter
– NodeJS căn bản cho người mới bắt đầu

Nhiều đối tượng trong Node.js sinh ra những sự kiện, ví dụ web.Server sinh ra 1 sự kiện mỗi lúc có 1 kết nối ngang hàng tới nó, hay fs.readStream sinh ra sự kiện khi 1 file được mở. Toàn bộ những đối tượng này đều là sự thể hiện của lớp occasions.EventEmitter trong Node.js.

1. Lớp EventEmitter trong Node.js

Lớp EventEmitter nằm trong occasions Module. Lớp này được truy cập qua cú pháp sau:
// Import occasions module
var occasions = require('occasions');

// Create an occasionEmitter object
var occasionEmitter = new occasions.EventEmitter();
Khi 1 EventEmitter gặp bất cứ lỗi nào, nó sẽ sinh ra 1 Error Event. Khi 1 Listener mới được cho thêm, sự kiện ‘newListener’ sẽ được bật và 1 Listener sẽ bị xóa bỏ, sự kiện ‘take awayListener’ sẽ được bật.
Event Emitter cung cấp khá nhiều thuộc tính như on hay emit. Thuộc tính on được dùng để gắn kết 1 hàm với sự kiện, và emit dược sử dụng để bật 1 sự kiện.
Những phương thức của lớp EventEmitter trong Node.js
  1. addListener(occasion, listener) : Thêm 1 Listener vào phần cuối của mảng các Listener cho 1 sự kiện chi tiết
  2. on(occasion, listener) : Thêm 1 Listener vào phần cuối của mảng các Listener cho 1 sự kiện chi tiết
  3. as soon as(occasion, listener) : Thêm 1 One-Time Listener cho sự kiện. Listener dạng này sẽ chỉ được gọi khi sự kiện được bật, rồi nó sẽ bị xóa
  4. take awayListener(occasion, listener) : Xóa 1 Listener ra khỏi mảng các Listener cho 1 sự kiện nào đấy.
  5. removeAllListeners([event]) : Xóa toàn bộ Listener của 1 sự kiện
  6. setMaxListeners(n) : Theo mặc định, lớp EventEmitters sẽ in 1 lời thông báo nếu như bạn thêm nhiều hơn 10 Listener cho 1 sự kiện chi tiết. Việc này tương đối có ích, bởi nó sẽ giúp bạn tìm ra những lỗi gây rò rỉ bộ nhớ. Dĩ nhiên, không phải toàn bộ các Emitters đều cần được giới hạn với con số là 10. Hàm này giúp bạn tăng con số đó. Thiết lập nó về 0 để không giới hạn lượng Listener cần thêm
  7. listeners(occasion)Trả về 1 mảng gồm các Listener cho 1 sự kiện chi tiết nào đấy
  8. emit(occasion, [arg1], [arg2], […])
    Thực thi từng Listener với những tham số đã cho. Trả về true nếu như sự kiện có các Listener, và false nếu như không có

2. Tìm hiểu EventEmitter:

a. Cơ chế hoạt động căn bản của EventEmitter :
Cơ chế hoạt động căn bản của EventEmitter trong Nodejs có thể được thể hiện qua ví dụ sau:
Trước tiên, mình tạo file emitter.js như dưới đây:
perform Emitter() {
  this.occasions = {};
}

Emitter.prototype.on = perform (kind, listener) 
  this.occasions[type] = this.occasions[type] ;

Emitter.prototype.emit = perform (kind) {
  if (this.occasions[type]) {
    this.occasions[type].forEach(perform(listener) {
      listener();
    });
  }
};

module.exports = Emitter;
Module Emitter gồm 1 literal object occasions để lưu lại toàn bộ những sự kiện; 2 phương thức prototype là on và emit để sử dụng khi kế thừa prototype. Phương thức on sẽ lưu lại theo từng kiểu sự kiện, mỗi kiểu sự kiện có 1 mảng những sự kiện khác nhau và các listener của mỗi kiểu sự kiện sẽ lưu giữ trong mảng riêng của nó. Phương thức emit được sử dụng để kiểm tra xem trong object lưu lại sự kiện đã có sự kiện đó chưa, nếu như có sẽ thực hiện phát các listener trong sự kiện đó ra.
Để giới thiệu cho Emitter này mình minh họa qua đoạn mã sau trong app.js:
var Emitter = require("./emitter");
var emitter = new Emitter();

emitter.on("denvang", perform tocdo() {
  console.log("Giảm tốc độ");
});

emitter.on("denvang", perform dunglai() {
  console.log("Dừng lại trước vạch giới hạn");
});

var tinhieu = [3, 2, 1];//1: đèn đỏ, 2: đèn vàng, 3: đèn xanh
for (var th of tinhieu) {
  if (th == 2) {
    emitter.emit("denvang");
  }
}
Chương trình giới thiệu việc nếu như tín hiệu giao thông là đèn vàng sẽ phát ra hiệu lệnh trong sự kiện denvang. Trong emitter, mình đã thêm vào object sự kiện, kiểu sự kiện là denvang. Trong kiểu sự kiện đèn vàng có 2 listener là tocdo và dunglai. Khi đèn tín hiệu rơi vào đèn vàng sẽ phát ra các listener đã lưu trong sự kiện với kiểu là denvang. Và kết quả ta được nhận khi gặp đèn vàng sẽ là:
Giảm tốc độ
Dừng lại trước vạch giới hạn
b. Kế thừa EventEmitter :
Sử dụng hàm có sẵn ở trong thư viện Events(Node.js)
Như mình có giới thiệu qua EventEmitter là 1 class trong thư việc Events (Node.js) với cơ chế hoạt động được mô phỏng ở phần trên. Trong thực tế, mình không nhất thiết phải viết 1 module giống nhau như bên trên để thực thi những sự kiện lắng nghe mà sử dụng trực tiếp các hàm có sẵn ở trong thư viện Events (Node.js). Để mô phỏng cho điều đó, cũng với ví dụ ở phần trước, mình sẽ minh họa cách sử dụng hàm on và emit sẵn có trong thư viện Events:
var Emitter = require("occasions");
var emitter = new Emitter();

emitter.on("denvang", perform tocdo() {
  console.log("Giảm tốc độ");
});

emitter.on("denvang", perform dunglai() {
  console.log("Dừng lại trước vạch giới hạn");
});

var tinhieu = [3, 2, 1];//1: đèn đỏ, 2: đèn vàng, 3: đèn xanh
for (var th of tinhieu) {
  if (th == 2) {
    emitter.emit("denvang");
  }
}
Thay vì require tới module emitter.js do chính mình tự tạo, mình sử require tới thư viện occasions, và qua kế thừa prototype các hàm sẵn có trong thư viện này. Cuối cùng, mình vẫn được nhận kết quả giống nhau cho ví dụ minh họa trên.
Kể thừa và tái sử dụng EventEmitter :
Để kế thừa và tái sử dụng EventEmitter, tại đây, mình giới thiệu cách sử dụng hàm inherits trong thư viện util (Node.js).
Để minh họa, mình tạo apptinhieu.js như dưới đây:
var EventEmitter = require("occasions");
var util = require("util");

perform DenTinHieu(typeOfLight) 

util.inherits(DenTinHieu, EventEmitter);

DenTinHieu.prototype.hieuLenh = perform () {
  change(this.kind) {
    case "dendo":
      this.emit("dunglai");
      break;
    case "denvang":
      this.emit("tocdo");
      this.emit("dunglai");
      break;
    case "denxanh":
      this.emit("duocphepdi");
      break;
    default:
      khongCoHieuLenh();
  }

};

var dendo = new DenTinHieu("dendo");

dendo.on("dunglai", dungLai);

var denvang = new DenTinHieu("denvang");

denvang.on("tocdo", tocDo);

denvang.on("dunglai", dungLai);

var denxanh = new DenTinHieu("denxanh");

denxanh.on("duocphepdi", duocPhepDi);

var tinhieu = {1: "dendo", 2: "denvang", 3: "denxanh"};
var tinhieuden = [1, 3, 2, 1];

for(var th of tinhieuden) {
  console.log("Tín hiệu đèn giao thông: ", tinhieu[th]);
  change(tinhieu[th]) {
    case "dendo":
      dendo.hieuLenh();
      break;
    case "denvang":
      denvang.hieuLenh();
      break;
    case "denxanh":
      denxanh.hieuLenh();
      break;
    default:
      khongCoHieuLenh();
   }
  }

perform dungLai() {
  console.log("Dừng lại trước vạch giới hạn");
}

perform tocDo() {
  console.log("Giảm tốc độ");
}

perform duocPhepDi() {
  console.log("Được phép đi");
}

perform khongCoHieuLenh() {
  console.log("Không có hiệu lệnh cho loại đèn tín hiệu này.");
}
Tại đây mình có 1 đối tượng là DenTinHieu. Đối tượng này sẽ kế thừa những phương thức của EventEmitter trong thư viện occasions (Node.js). Mình mở rộng đối tượng này qua prototype bằng hàm hieuLenh để thực thi hiệu lệnh của loại đèn tín hiệu.
Từ đối tượng DenTinHieu này mình tạo nên 3 đối tượng dendo, denvang, denxanh là thể hiện của DenTinHieu. Vì đối tượng DenTinHieu được thừa kế từ EventEmitter nên các thể hiện của nó có thể sử dụng những phương thức của EventEmitter mà không phải phải kế thừa lại. Chính vì thế những phương thức này được tái sử dụng. Với cách này, mình thiết lập những sự kiện qua phương thức on cho những đối tượng DenTinHieu và phát những sự kiện này ra qua phương thức emit.
Thực thi apptinhieu.js trên terminal nodejs apptinhieu.js, mình được kết quả như dưới đây:
Tin hieu đèn giao thông: dendo
Dừng lại trước vạch giới hạn
Tin hieu đèn giao thông: denxanh
Được phép đi
Tin hieu đèn giao thông: denvang
Giảm tốc độ
Dừng lại trước vạch giới hạn
Tin hieu đèn giao thông: dendo
Dừng lại trước vạch giới hạn
1 cách khác, mình sẽ không tạo nên 3 thể hiện của DenTinHieu như ở trên nữa mà sử dụng 1 thể hiện chung cho cả 3 loại trên. Bây giờ, mình xem 3 loại đèn tín hiện là 1 information đầu vào cho phương thức on và emit.
DenTinHieu.prototype.hieuLenh = perform (loaiDen) {
  console.log(`Tin hieu $this.kind: ${loaiDen}`);
  this.emit("canhbao", loaiDen);
};

var denGiaoThong = new DenTinHieu("đèn giao thông");

denGiaoThong.on("canhbao", perform(loaiDen) {
  change(loaiDen) {
    case "dendo":
      dungLai();
      break;
    case "denvang":
      tocDo();
      dungLai();
      break;
    case "denxanh":
      duocPhepDi();
      break;
    default:
      khongCoHieuLenh();
  }
});
Tại đây, thể hiện denGiaoThong được tạo nên từ DenTinHieu sẽ đại diện chung cho những loại đèn tín hiệu. Khi thiết lập sự kiện canhbao cho denGiaoThong mình truyền information là loaiden vào phương thức thực thi để xử lý khi emit sự kiện canhbao trên denGiaoThong.
Hoặc mình có thể thực hiện thiết lập sự kiện trên EventEmitter, rồi, đối tượng DenTinHieu sẽ kế thừa từ EventEmitter. Bây giờ thể hiện denGiaoThong cũng có sự kiện canhbao được kế thừa, và chỉ việc phát sự kiện ra qua phương thức emit.
EventEmitter.on("canhbao", perform(loaiDen) {
  change(loaiDen) {
    case "dendo":
      dungLai();
      break;
    case "denvang":
      tocDo();
      dungLai();
      break;
    case "denxanh":
      duocPhepDi();
      break;
    default:
      khongCoHieuLenh();
  }
});

util.inherits(DenTinHieu, EventEmitter);

DenTinHieu.prototype.hieuLenh = perform (loaiDen) {
  console.log(`Tin hieu $this.kind: ${loaiDen}`);
  this.emit("canhbao", loaiDen);
};

3. Những sự kiện của lớp EventEmitter trong Node.js

1. newListener
  • occasion – Dạng chuỗi, biểu diễn tên sự kiện
  • listener – Tên hàm xử lý sự kiện
Sự kiện này được sinh bất kỳ lúc nào bạn thêm 1 Listener. Khi sự kiện này được bật, Listener có thể sẽ chưa được cho thêm vào mảng Listener của sự kiện
2. take awayListener
  • occasion – Dạng chuỗi, biểu diễn tên sự kiện
  • listener – Tên hàm xử lý sự kiện
Sự kiện này xảy ra bất kỳ lúc nào có ai đó xóa 1 Listener. Khi 1 sự kiện được bật, Listener này chưa được xóa khỏi mảng Listener của sự kiện

4. Ví dụ minh họa lớp EventEmitter trong Node.js

Tạo 1 file js với tên là fundamental.js với nội dụng Node.js như sau đây. Trong fundamental.js, đầu tiên bạn khai báo occasions Module do sử dụng phương thức require(). Tiếp đó, bạn sử dụng phương thức addListener() để thêm 1 Listener cho 1 sự kiện nào đấy, và sử dụng các thuộc tính on và emit để thực hiện các tính năng đã trình bày bên trên.
var occasions = require('occasions');
var occasionEmitter = new occasions.EventEmitter();

// listener #1
var listner1 = perform listner1() {
   console.log('listner1 executed.');
}

// listener #2
var listner2 = perform listner2() {
   console.log('listner2 executed.');
}

// Bind the connection occasion with the listner1 perform
occasionEmitter.addListener('connection', listner1);

// Bind the connection occasion with the listner2 perform
occasionEmitter.on('connection', listner2);

var occasionListeners = require('occasions').EventEmitter.listenerCount
   (occasionEmitter,'connection');
console.log(occasionListeners + " Listner(s) listening to connection occasion");

// Fire the connection occasion 
occasionEmitter.emit('connection');

// Remove the binding of listner1 perform
occasionEmitter.take awayListener('connection', listner1);
console.log("Listner1 is not going to pay attention now.");

// Fire the connection occasion 
occasionEmitter.emit('connection');

occasionListeners = require('occasions').EventEmitter.listenerCount(occasionEmitter,'connection');
console.log(occasionListeners + " Listner(s) listening to connection occasion");

console.log("Program Ended.");
Chạy fundamental.js để xem kết quả:
$ node fundamental.js
Kết quả hiển thị như dưới đây :
2 Listner(s) listening to connection occasion
listner1 executed.
listner2 executed.
Listner1 is not going to pay attention now.
listner2 executed.
1 Listner(s) listening to connection occasion
Program Ended.

 

 

admin

Leave a Reply

Your email address will not be published. Required fields are marked *