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