introduction:
On node Another important and basic pattern used in JS is the observer pattern, which is also one of the pillars of the platform and a prerequisite for using the node core and user module
The observer defines an ideal solution for building nodes JS response characteristics, and is a perfect complement to callback. A formal definition is given below:
The observer pattern defines an object (called a subject) that can notify a group of observers (or listeners) when its reaction state changes
The main difference from the callback mode is that the subject can actually notify multiple observers, while the traditional CPS callback usually propagates its results to one listener, that is, the callback
EventEmitter class
In traditional object-oriented programming, the observer pattern requires interfaces, entity classes and hierarchies JS, everything becomes simple. The observer mode is built into the kernel and can be obtained through the EventEmitter class. EventEmitter class allows us to register one or more functions as listeners. When a specific time type is triggered, it will be called:
┌───────────────────────────────────────────────────────────┐ │ │ │ ┌────────────┐ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ┌──►│ Listener │ │ │ │ │ │ │ │ ┌─┬────────────┐ │ │ │ │ │ │ │ │ │ │ │ │ │ ┌─────────────┼─┤ │ │ └────────────┘ │ │ │ │ │ EventA ├─┤ │ │ │ │ │ │ │ ┌────────────┐ │ │ │ └─┼────────────┘ └──►│ │ │ │ │ EventEmitter │ │ Listener │ │ │ │ ┌─┼────────────┐ ┌──►│ │ │ │ │ │ │ │ │ └────────────┘ │ │ │ │ │ EventB ├─┤ │ │ └─────────────┼─┤ │ │ ┌────────────┐ │ │ │ │ │ │ │ │ │ │ └─┴────────────┘ └───► │ │ │ │ │ │ │ │ Listener │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └────────────┘ │ │ │ └───────────────────────────────────────────────────────────┘
The EventEmitter class can be obtained from the events module provided by node
let EventEmitter = require('events').EventEmitter
Here are some common methods
- on
- once
- emit
- removeListner
The method of EventEmitter is described in detail on the official website http://nodejs.cn/api/events.html
Create and use EventEmitter
let EventEmitter = require('events').EventEmitter let fs = require('fs') function findPattern(files, regex) { let emitter = new EventEmitter() files.forEach(file => { fs.readFile(file, 'utf8', (err, result) => { if (err) { return emitter.emit('error', err) //When the file cannot be found, register and trigger the err event to pass the error to the listener } emitter.emit('fileread', file) //Register and trigger the fileread event to pass the file name to the listener let match if (match = result.match(regex)) { emitter.emit('find', match) //Register and trigger the find event to pass the content to the listener } }) }); return emitter } findPattern( ['fileA.txt', 'fileB.json', 'df.json'], /hello \w+/g ) .on('fileread', file => console.log(file + ' was read ) //Listen to the fileread event. File receives the file name of the open file .on('find', match => console.log(match + ' was find)) //Listen for the find time. match is the string array found by regular matching .on('error', err=> console.log(err+ ' happend)) //Listen for the find time. match is the string array found by regular matching
Make any object observable
Sometimes, it is not enough to create a new observable object directly from the EventEmitter class, because it is impractical to provide functions other than generating new events. It is easy to expand the EventEmitter through the inherits function provided by the core module util
class filePatten extends EventEmitter { constructor(regex) { super() this.files = [] this.regex = regex } add(fileName) { this.files.push(fileName) return this } find() { this.files.forEach(file => { fs.readFile(file, 'utf8', (err, result) => { if (err) { this.emit('error', err) } this.emit('fileread', file) let match if (match = result.match(this.regex)) { this.emit('find', match) } }) }) return this } } let mode = new filePatten(/hello \w+/g) mode.add('fileA.txt') .add('fileB.json') .find() .on('fileread', file => console.log(file + 'has read')) .on('find', match => console.log(match))
By inheriting the function of EventEmitter, we can see how the filePatten object has a complete set of methods. In order to keep similar to the methods of EventEmitter class, we let the custom methods return themselves, return this, and keep the style consistent, because EventEmitter prototpye. On and other methods also return this by default
Listen to the event before triggering
About the above code, you need to pay attention to one detail, that is The on method is first due to emit() is executed due to FS Readfile is an asynchronous function, which is monitored first and triggered later. Only in this way can our code successfully listen to events and execute callback functions. This is an error prone point. Next, we will give an error use case
Synchronous and asynchronous events
Like callbacks, events can be issued synchronously or asynchronously, which is Node.js basic design pattern callback pattern It is mentioned in the CPS in this article, but it is important not to mix the two methods in the same EventEmitter
The main difference between sending synchronous events and sending asynchronous events lies in the way of listener registration. When events are sent asynchronously, that is, after EventEmitter is initialized, the program still has events to register new listeners (this is what we discussed earlier, and the registered events are in asynchronous functions)
Instead, sending events synchronously requires registering all listeners before the EventEmitter function starts issuing any events. Let's see an example of the opposite
let EventEmitter = require('events').EventEmitter class syncEmit extends EventEmitter{ constructor() { super() this.emit('init') //Synchronously register and trigger the init event, but there is no observer listening to this event at this time } } let sync = new syncEmit().on('init',() => console.log('init success')) // Therefore, this statement will not be output
A little modification
let EventEmitter = require('events').EventEmitter class syncEmit extends EventEmitter{ constructor() { super() } } let syncobj= new syncEmit().on('init',() => console.log('init success')) syncobj.emit('init') //=>'init success' is triggered after listening
Choose event emitter or callback mode?
When defining asynchronous API s, the common difficulty is how to judge whether EventEmitter or callback should be used. The general principle is: when the result must be returned asynchronously, callback should be used; Use events when you need to communicate what just happened.
However, in addition to this simple principle, since most of the events in the two examples have the same effect and can achieve the same effect, there is a lot of confusion, such as:
function helloEvents() { let eventEmitter = new EventEmitter() setTimeout(() => { eventEmitter.emit('sayhello', 'hello world') }) } function helloCallback(callback) { setTimeout(() => { callback('hello world') }) }
The two functions helloEvents() and helloCallback() can be considered functionally equivalent.
- The first uses events to convey the completion of the timeout
- The second uses a callback to notify the caller
As a first observation, we can say that callbacks have some limitations when supporting different types of events. In fact, we can still pass the type as the parameter of the callback function, for example:
function helloCallback(callback) { //... callback('init') //Callback init event //... callback('read') //Callback read event //... callback('end') //Callback end event //... } function fun(type) { switch(type) { //Processing in callback function case 'init' : //... case 'read' : //... } } helloCallback(fun)
Or receive several callbacks to distinguish different events by calling:
function helloCallback(callbackInit, callbackRead, callbackEnd) { //... callbackInit() //Callback init event //... callbackRead() //Callback read event //... callbackEnd() //Callback end event //... } helloCallback(fun1,fun2,fun3)
However, you can't think of this as an elegant API. In this case, it is obvious that EventEmitter can provide better structure and leaner code
Another case where EventEmitter is preferred is that the same event may occur multiple times or not at all. The callback function can only be called once, regardless of whether the operation is successful or not. In fact, there is a possible repetition, which makes us reconsider the semantic characteristics of the event, which is more like an event that must be communicated than a result. In this case, EventEmitter is the best choice
last
- The API using callbacks can only notify specific callbacks, while the EventEmitter function can make multiple listeners receive the same notification