From 6317502d108b40986eee35265b63a6a690e606ad Mon Sep 17 00:00:00 2001 From: Deokjin Kim Date: Thu, 19 Jan 2023 04:25:11 +0900 Subject: [PATCH] doc: update events API example to have runnable code Fixes: https://github.com/nodejs/node/issues/45755 PR-URL: https://github.com/nodejs/node/pull/45760 Reviewed-By: Luigi Pinca Reviewed-By: Tierney Cyren Reviewed-By: James M Snell --- doc/api/events.md | 297 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 281 insertions(+), 16 deletions(-) diff --git a/doc/api/events.md b/doc/api/events.md index b53c6a6bacaf11..0b61c1add6f20e 100644 --- a/doc/api/events.md +++ b/doc/api/events.md @@ -62,7 +62,26 @@ an ordinary listener function is called, the standard `this` keyword is intentionally set to reference the `EventEmitter` instance to which the listener is attached. -```js +```mjs +import { EventEmitter } from 'node:events'; +class MyEmitter extends EventEmitter {} +const myEmitter = new MyEmitter(); +myEmitter.on('event', function(a, b) { + console.log(a, b, this, this === myEmitter); + // Prints: + // a b MyEmitter { + // _events: [Object: null prototype] { event: [Function (anonymous)] }, + // _eventsCount: 1, + // _maxListeners: undefined, + // [Symbol(kCapture)]: false + // } true +}); +myEmitter.emit('event', 'a', 'b'); +``` + +```cjs +const EventEmitter = require('node:events'); +class MyEmitter extends EventEmitter {} const myEmitter = new MyEmitter(); myEmitter.on('event', function(a, b) { console.log(a, b, this, this === myEmitter); @@ -80,7 +99,20 @@ myEmitter.emit('event', 'a', 'b'); It is possible to use ES6 Arrow Functions as listeners, however, when doing so, the `this` keyword will no longer reference the `EventEmitter` instance: -```js +```mjs +import { EventEmitter } from 'node:events'; +class MyEmitter extends EventEmitter {} +const myEmitter = new MyEmitter(); +myEmitter.on('event', (a, b) => { + console.log(a, b, this); + // Prints: a b {} +}); +myEmitter.emit('event', 'a', 'b'); +``` + +```cjs +const EventEmitter = require('node:events'); +class MyEmitter extends EventEmitter {} const myEmitter = new MyEmitter(); myEmitter.on('event', (a, b) => { console.log(a, b, this); @@ -97,7 +129,21 @@ events and helps avoid race conditions and logic errors. When appropriate, listener functions can switch to an asynchronous mode of operation using the `setImmediate()` or `process.nextTick()` methods: -```js +```mjs +import { EventEmitter } from 'node:events'; +class MyEmitter extends EventEmitter {} +const myEmitter = new MyEmitter(); +myEmitter.on('event', (a, b) => { + setImmediate(() => { + console.log('this happens asynchronously'); + }); +}); +myEmitter.emit('event', 'a', 'b'); +``` + +```cjs +const EventEmitter = require('node:events'); +class MyEmitter extends EventEmitter {} const myEmitter = new MyEmitter(); myEmitter.on('event', (a, b) => { setImmediate(() => { @@ -112,7 +158,23 @@ myEmitter.emit('event', 'a', 'b'); When a listener is registered using the `eventEmitter.on()` method, that listener is invoked _every time_ the named event is emitted. -```js +```mjs +import { EventEmitter } from 'node:events'; +class MyEmitter extends EventEmitter {} +const myEmitter = new MyEmitter(); +let m = 0; +myEmitter.on('event', () => { + console.log(++m); +}); +myEmitter.emit('event'); +// Prints: 1 +myEmitter.emit('event'); +// Prints: 2 +``` + +```cjs +const EventEmitter = require('node:events'); +class MyEmitter extends EventEmitter {} const myEmitter = new MyEmitter(); let m = 0; myEmitter.on('event', () => { @@ -128,7 +190,23 @@ Using the `eventEmitter.once()` method, it is possible to register a listener that is called at most once for a particular event. Once the event is emitted, the listener is unregistered and _then_ called. -```js +```mjs +import { EventEmitter } from 'node:events'; +class MyEmitter extends EventEmitter {} +const myEmitter = new MyEmitter(); +let m = 0; +myEmitter.once('event', () => { + console.log(++m); +}); +myEmitter.emit('event'); +// Prints: 1 +myEmitter.emit('event'); +// Ignored +``` + +```cjs +const EventEmitter = require('node:events'); +class MyEmitter extends EventEmitter {} const myEmitter = new MyEmitter(); let m = 0; myEmitter.once('event', () => { @@ -150,7 +228,17 @@ If an `EventEmitter` does _not_ have at least one listener registered for the `'error'` event, and an `'error'` event is emitted, the error is thrown, a stack trace is printed, and the Node.js process exits. -```js +```mjs +import { EventEmitter } from 'node:events'; +class MyEmitter extends EventEmitter {} +const myEmitter = new MyEmitter(); +myEmitter.emit('error', new Error('whoops!')); +// Throws and crashes Node.js +``` + +```cjs +const EventEmitter = require('node:events'); +class MyEmitter extends EventEmitter {} const myEmitter = new MyEmitter(); myEmitter.emit('error', new Error('whoops!')); // Throws and crashes Node.js @@ -161,7 +249,20 @@ used. (Note, however, that the `node:domain` module is deprecated.) As a best practice, listeners should always be added for the `'error'` events. -```js +```mjs +import { EventEmitter } from 'node:events'; +class MyEmitter extends EventEmitter {} +const myEmitter = new MyEmitter(); +myEmitter.on('error', (err) => { + console.error('whoops! there was an error'); +}); +myEmitter.emit('error', new Error('whoops!')); +// Prints: whoops! there was an error +``` + +```cjs +const EventEmitter = require('node:events'); +class MyEmitter extends EventEmitter {} const myEmitter = new MyEmitter(); myEmitter.on('error', (err) => { console.error('whoops! there was an error'); @@ -200,7 +301,16 @@ myEmitter.emit('error', new Error('whoops!')); Using `async` functions with event handlers is problematic, because it can lead to an unhandled rejection in case of a thrown exception: -```js +```mjs +import { EventEmitter } from 'node:events'; +const ee = new EventEmitter(); +ee.on('something', async (value) => { + throw new Error('kaboom'); +}); +``` + +```cjs +const EventEmitter = require('node:events'); const ee = new EventEmitter(); ee.on('something', async (value) => { throw new Error('kaboom'); @@ -213,7 +323,25 @@ handler on the `Promise`. This handler routes the exception asynchronously to the [`Symbol.for('nodejs.rejection')`][rejection] method if there is one, or to [`'error'`][error] event handler if there is none. -```js +```mjs +import { EventEmitter } from 'node:events'; +const ee1 = new EventEmitter({ captureRejections: true }); +ee1.on('something', async (value) => { + throw new Error('kaboom'); +}); + +ee1.on('error', console.log); + +const ee2 = new EventEmitter({ captureRejections: true }); +ee2.on('something', async (value) => { + throw new Error('kaboom'); +}); + +ee2[Symbol.for('nodejs.rejection')] = console.log; +``` + +```cjs +const EventEmitter = require('node:events'); const ee1 = new EventEmitter({ captureRejections: true }); ee1.on('something', async (value) => { throw new Error('kaboom'); @@ -310,7 +438,31 @@ but important side effect: any _additional_ listeners registered to the same `name` _within_ the `'newListener'` callback are inserted _before_ the listener that is in the process of being added. -```js +```mjs +import { EventEmitter } from 'node:events'; +class MyEmitter extends EventEmitter {} + +const myEmitter = new MyEmitter(); +// Only do this once so we don't loop forever +myEmitter.once('newListener', (event, listener) => { + if (event === 'event') { + // Insert a new listener in front + myEmitter.on('event', () => { + console.log('B'); + }); + } +}); +myEmitter.on('event', () => { + console.log('A'); +}); +myEmitter.emit('event'); +// Prints: +// B +// A +``` + +```cjs +const EventEmitter = require('node:events'); class MyEmitter extends EventEmitter {} const myEmitter = new MyEmitter(); @@ -569,7 +721,19 @@ By default, event listeners are invoked in the order they are added. The `emitter.prependListener()` method can be used as an alternative to add the event listener to the beginning of the listeners array. -```js +```mjs +import { EventEmitter } from 'node:events'; +const myEE = new EventEmitter(); +myEE.on('foo', () => console.log('a')); +myEE.prependListener('foo', () => console.log('b')); +myEE.emit('foo'); +// Prints: +// b +// a +``` + +```cjs +const EventEmitter = require('node:events'); const myEE = new EventEmitter(); myEE.on('foo', () => console.log('a')); myEE.prependListener('foo', () => console.log('b')); @@ -604,7 +768,19 @@ By default, event listeners are invoked in the order they are added. The `emitter.prependOnceListener()` method can be used as an alternative to add the event listener to the beginning of the listeners array. -```js +```mjs +import { EventEmitter } from 'node:events'; +const myEE = new EventEmitter(); +myEE.once('foo', () => console.log('a')); +myEE.prependOnceListener('foo', () => console.log('b')); +myEE.emit('foo'); +// Prints: +// b +// a +``` + +```cjs +const EventEmitter = require('node:events'); const myEE = new EventEmitter(); myEE.once('foo', () => console.log('a')); myEE.prependOnceListener('foo', () => console.log('b')); @@ -710,7 +886,41 @@ time of emitting are called in order. This implies that any _before_ the last listener finishes execution will not remove them from `emit()` in progress. Subsequent events behave as expected. -```js +```mjs +import { EventEmitter } from 'node:events'; +class MyEmitter extends EventEmitter {} +const myEmitter = new MyEmitter(); + +const callbackA = () => { + console.log('A'); + myEmitter.removeListener('event', callbackB); +}; + +const callbackB = () => { + console.log('B'); +}; + +myEmitter.on('event', callbackA); + +myEmitter.on('event', callbackB); + +// callbackA removes listener callbackB but it will still be called. +// Internal listener array at time of emit [callbackA, callbackB] +myEmitter.emit('event'); +// Prints: +// A +// B + +// callbackB is now removed. +// Internal listener array [callbackA] +myEmitter.emit('event'); +// Prints: +// A +``` + +```cjs +const EventEmitter = require('node:events'); +class MyEmitter extends EventEmitter {} const myEmitter = new MyEmitter(); const callbackA = () => { @@ -751,7 +961,24 @@ event (as in the example below), `removeListener()` will remove the most recently added instance. In the example the `once('ping')` listener is removed: -```js +```mjs +import { EventEmitter } from 'node:events'; +const ee = new EventEmitter(); + +function pong() { + console.log('pong'); +} + +ee.on('ping', pong); +ee.once('ping', pong); +ee.removeListener('ping', pong); + +ee.emit('ping'); +ee.emit('ping'); +``` + +```cjs +const EventEmitter = require('node:events'); const ee = new EventEmitter(); function pong() { @@ -797,7 +1024,33 @@ added: v9.4.0 Returns a copy of the array of listeners for the event named `eventName`, including any wrappers (such as those created by `.once()`). -```js +```mjs +import { EventEmitter } from 'node:events'; +const emitter = new EventEmitter(); +emitter.once('log', () => console.log('log once')); + +// Returns a new Array with a function `onceWrapper` which has a property +// `listener` which contains the original listener bound above +const listeners = emitter.rawListeners('log'); +const logFnWrapper = listeners[0]; + +// Logs "log once" to the console and does not unbind the `once` event +logFnWrapper.listener(); + +// Logs "log once" to the console and removes the listener +logFnWrapper(); + +emitter.on('log', () => console.log('log persistently')); +// Will return a new Array with a single function bound by `.on()` above +const newListeners = emitter.rawListeners('log'); + +// Logs "log persistently" twice +newListeners[0](); +emitter.emit('log'); +``` + +```cjs +const EventEmitter = require('node:events'); const emitter = new EventEmitter(); emitter.once('log', () => console.log('log once')); @@ -907,7 +1160,19 @@ that a "possible EventEmitter memory leak" has been detected. For any single `EventEmitter`, the `emitter.getMaxListeners()` and `emitter.setMaxListeners()` methods can be used to temporarily avoid this warning: -```js +```mjs +import { EventEmitter } from 'node:events'; +const emitter = new EventEmitter(); +emitter.setMaxListeners(emitter.getMaxListeners() + 1); +emitter.once('event', () => { + // do stuff + emitter.setMaxListeners(Math.max(emitter.getMaxListeners() - 1, 0)); +}); +``` + +```cjs +const EventEmitter = require('node:events'); +const emitter = new EventEmitter(); emitter.setMaxListeners(emitter.getMaxListeners() + 1); emitter.once('event', () => { // do stuff