The Node.js core API is based on an asynchronous event-driven architecture, and fs.readstream can listen for events by inheriting the EventEmitter class from on(), as shown below
const fs = require('fs');
const EventEmitter = require('events');
var stream = fs.createReadStream('./a.js');
console.log(stream instanceof EventEmitter); // true
Copy the code
In addition to streams, Net. Server, and Processes inherit from EventEmitter so they can listen for events.
const EventEmitter = require('events');
const net = require('net');
var server = net.createServer(function(client) {
console.log(client instanceof EventEmitter); // true
});
server.listen(8000, () => {
console.log('server started on port 8000');
});
console.log(process instanceof EventEmitter); // true
Copy the code
The name of the event that on listens for can contain special characters (‘$’, ‘* ‘, ‘~’ are ok), but it is case sensitive.
const EventEmitter = require('events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
myEmitter.on('* $~', () => {
console.log('an event occurred! ');
});
myEmitter.emit('* $~');
Copy the code
When an EventEmitter object emits an event, all functions bound to the event are called synchronously. The return value of the bound function call is ignored (this can cause other problems, which will be discussed later). However, if the object is modified, it can be passed to other listener functions, such as:
const EventEmitter = require('events');
class MyEmitter extends EventEmitter{};
const myEmitter = new MyEmitter();
myEmitter.on('event'.function(data) {
console.log(data.num); // 1
data.num++;
});
myEmitter.on('event', (data) => {
console.log(data.num); // 2
});
myEmitter.emit('event', {
num: 1
});
Copy the code
This is a JS feature about reference types, which has nothing to do with EventEmitter. In practice, it is not recommended because of low maintainability.
It is possible to transfer execution results between functions when implementing a mechanism like EventEmitter (for example, a.ipe (b).pipe(c))
Synchronous or asynchronous
When EventEmitter fires events, calls to listener functions are synchronous (note that the ‘end’ output comes at the end), but that doesn’t mean that listeners can’t include asynchronous code (as listener2 below shows).
const EventEmitter = require('events');
class MyEmitter extends EventEmitter{};
const myEmitter = new MyEmitter();
myEmitter.on('event'.function() {
console.log('listener1');
});
myEmitter.on('event', async function() {
console.log('listener2');
await new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 1000);
});
});
myEmitter.on('event'.function() {
console.log('listener3');
});
myEmitter.emit('event');
console.log('end'); Listener1 listener2 listener3 endCopy the code
Exception handling
Because the execution of the listener function is synchronous, code for synchronization can be caught through a try catch
const EventEmitter = require('events');
class MyEmitter extends EventEmitter{};
const myEmitter = new MyEmitter();
myEmitter.on('event'.function() {
a.b();
console.log('listener1');
});
myEmitter.on('event', async function() {
console.log('listener2');
await new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 1000);
});
});
myEmitter.on('event'.function() {
console.log('listener3');
});
try {
myEmitter.emit('event');
} catch(e) {
console.error('err');
}
console.log('end'); // Output the result end errCopy the code
But if a.b(); Moving to the second listener causes the following problem
const EventEmitter = require('events');
class MyEmitter extends EventEmitter{};
const myEmitter = new MyEmitter();
myEmitter.on('event'.function() {
console.log('listener1');
});
myEmitter.on('event', async function() {
console.log('listener2');
a.b();
await new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 1000);
});
});
myEmitter.on('event'.function() {
console.log('listener3');
});
try {
myEmitter.emit('event');
} catch(e) {
console.error('err');
}
console.log('end'); / / output listener1 listener2 listener3 end (9046) node: UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): ReferenceError: a is not defined (node:9046) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zeroexit code
Copy the code
Async returns a Promise, a reject state if there is an error in the function. Node.js does not recommend ignoring reject promises, while EventEmitter ignores the return value of each listener function, which is why the above situation occurs. In this case, we need to add a try catch handler to the second listener.
When an event is raised, the event is silently ignored if there is no function attached to it, but if the event name is error and there is no event handling associated with it, the program crashes out
const EventEmitter = require('events');
class MyEmitter extends EventEmitter{};
const myEmitter = new MyEmitter();
myEmitter.on('event'.function(data) {
console.log(data);
});
myEmitter.emit('error'); events.js:199 throw err; ^ Error [ERR_UNHANDLED_ERROR]: Unhandled error. at MyEmitter.emit (events.js:197:19) at Object.<anonymous> (/Users/xiji/workspace/learn/event-emitter/b.js:7:11) at Module._compile (module.js:641:30) at Object.Module._extensions.. js (module.js:652:10) at Module.load (module.js:560:32) at tryModuleLoad (module.js:503:12) at Function.Module._load (module.js:495:3) at Function.Module.runMain (module.js:682:10) at startup (bootstrap_node.js:191:16) at bootstrap_node.js:613:3Copy the code
The program will not exit unless a handler for error events is added.
Another way is for Process to listen for uncaughtExceptions, but this is not recommended because uncaughtExceptions are very serious. Normally uncaughtException handlers do some reporting or cleaning and then execute process.exit(1) to exit the program.
process.on('uncaughtException'.function(err) {
console.error('uncaught exception:', err.stack || err);
// orderly close server, resources, etc.
closeEverything(function(err) {
if (err)
console.error('Error while closing everything:', err.stack || err);
// exit anyway
process.exit(1);
});
});
Copy the code
Listen to a
If multiple uncaught exceptions occur at the same time, closeEverything may be triggered multiple times, which may cause new problems. Therefore, it is recommended that only the first uncaught Excepition be monitored, in which case the once method is needed
process.once('uncaughtException'.function(err) {
// orderly close server, resources, etc.
closeEverything(function(err) {
if (err)
console.error('Error while closing everything:', err.stack || err);
// exit anyway
process.exit(1);
});
});
Copy the code
For the second uncaughtException, there is no corresponding handler, which will cause the program to exit immediately. To solve this problem, we can add the error record of each exception in addition to once. As follows:
process.on('uncaughtException'.function(err) {
console.error('uncaught exception:', err.stack || err);
});
Copy the code
Listen for the order in which functions are executed
The previous example (on(eventName, Listener)) shows that the order in which listeners are executed is consistent with the order in which code is written. EventEmitter also provides other methods to adjust the order in which listeners are executed, although it is not as flexible as publishable and subscription-based piping.
In addition to on (append backwards), we can also use prependListener (insert forward) to add listeners
const EventEmitter = require('events');
class MyEmitter extends EventEmitter{};
const myEmitter = new MyEmitter();
myEmitter.prependListener('event'.function() {
console.log('listener1');
});
myEmitter.prependListener('event', async function() {
console.log('listener2');
});
myEmitter.prependListener('event'.function() {
console.log('listener3');
});
myEmitter.emit('event');
console.log('end'); Listener3 listener2 listener1 endCopy the code
EventEmiter triggers a ‘newListener’ event every time a newListener is added, so it is possible and possible to insert listeners forward by listening on this event, but it is important to avoid an infinite loop. If there is code to add a listener to newListener, use once for newListener.
const EventEmitter = require('events');
class MyEmitter extends EventEmitter{};
const myEmitter = new MyEmitter();
myEmitter.once('newListener', (event, listener) => {
if (event === 'event') {
myEmitter.on('event', () => {
console.log('B'); }); }}); myEmitter.on('event', () => {
console.log('A');
});
myEmitter.emit('event'); // Output result // B // ACopy the code
Adjust the default maximum listener
By default, the maximum number of listeners for a single event is 10. If there are more than 10 listeners, the listener will still be executed, but the console will have a warning message, and the alarm message will contain the operation suggestion. Limits to maximum listeners can be adjusted by calling emitters. SetMaxListeners ()
(node:9379) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 event listeners added. Use emitter.setMaxListeners() to increase limit
Copy the code
The warning information above is not granular enough to tell us where the code went wrong. You can get more detailed information from process.on(‘warning’) (Emitter, event, eventCount).
process.on('warning', (e) => {
console.log(e);
})
{ MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 event listeners added. Use emitter.setMaxListeners() to increase limitat _addListener (events.js:289:19) at MyEmitter.prependListener (events.js:313:14) at Object.<anonymous> (/Users/xiji/workspace/learn/event-emitter/b.js:34:11) at Module._compile (module.js:641:30) at Object.Module._extensions.. js (module.js:652:10) at Module.load (module.js:560:32) at tryModuleLoad (module.js:503:12) at Function.Module._load (module.js:495:3) at Function.Module.runMain (module.js:682:10) at startup (bootstrap_node.js:191:16) name:'MaxListenersExceededWarning',
emitter:
MyEmitter {
domain: null,
_events: { event: [Array] },
_eventsCount: 1,
_maxListeners: undefined },
type: 'event',
count: 11 }
Copy the code
This points to the
If we write this as an emitter, this points to an event
const EventEmitter = require('events');
class MyEmitter extends EventEmitter{};
const myEmitter = new MyEmitter();
myEmitter.on('event'.function(a, b) {
console.log(a, b, this === myEmitter); // a b true
});
myEmitter.emit('event'.'a'.'b');
Copy the code
If we write this as an arrow function, then this doesn’t point to the emitter
const EventEmitter = require('events');
class MyEmitter extends EventEmitter{};
const myEmitter = new MyEmitter();
myEmitter.on('event', (a, b) => {
console.log(a, b, this === myEmitter); // a b false
});
myEmitter.emit('event'.'a'.'b');
Copy the code
other
Emitter. Off (eventName, listener), emitter. RemoveListener (eventName, The listener), emitter. RemoveAllListeners ([eventName]) to remove surveillance. The function returns an Emitter object, so you can use chain syntax
Emitter. ListenerCount (eventName) Obtains the number of listeners registered for events
Emitters. Listeners (eventName) can obtain an array of listeners registered for events.
The resources
https://netbasal.com/javascript-the-magic-behind-event-emitter-cce3abcbcef9
https://medium.com/technoetics/node-js-event-emitter-explained-d4f7fd141a1a
https://medium.com/yld-engineering-blog/using-an-event-emitter-common-use-and-edge-cases-b5eb518a4bd2
https://medium.freecodecamp.org/understanding-node-js-event-driven-architecture-223292fcbc2d
https://nodejs.org/api/events.html