Event subscribing/emitting

JavaScript is a language that feels right at home when subscribing to and emitting Events. Events are incredibly common in most JavaScript programs, whether dealing with user-derived Events within the browser or server-side Events in Node.js.

There are various names used for operations relating to Events in JavaScript, so it's useful to know all of these names upfront so we're not confused when encountering them. An event is an occurrence in time that will result in the invocation of any callbacks that have been subscribed for that Event. Subscribing to an Event has many names, which all effectively mean the same thing: subscribing, registering, listening, binding, and so on. When the Event occurs, the subscribed callback is invoked. This, as well, has many names: invoking, calling, emitting, firing, or triggering. The actual function that is called can also have various names: function, callback, listener, or handler.

At its core, any abstraction that supports Events will usually do so by storing callbacks to be called later, keyed with specific Event names. We can imagine that a DOM element might store its Event listeners in a structure like the following:

{
"click": [Function, Function, Function],
"mouseover": [Function, Function],
"mouseout": [Function]
}

Any Event-supporting abstraction will simply store a series of callbacks to be called later. As such, when subscribing to an Event, you will need to provide both the callback you wish it to call and the Event name that it will be tied to. In the DOM, we would do this like so:

document,body.addEventListener('mousemove', e => {
e; // => the Event object
});

Here, we see that an Event object is passed to the callback. This is idiomatically named e or evt for succinctness. Most abstractions that provide an Events API will pass specific Event-related information to the callback. This may be in the form of a singular Event object or several arguments.

It's important to note that there truly is no single standard for Events although there are conventions that have emerged. Typically there will always be a method used to register or subscribe to an Event and then another to remove that subscription. The following is an example of using the Node.js Event-Emitter API, which is supported by the native HTTP module:

const server = http.createServer(...);

function onConnect(req, cltSocket, head) {
// Connect to an origin server...
}

// Subscribe
server.on('connect', onConnect);

// Unsubscribe
server.off('connect', onConnect);

Here, you can see that the on() method is used to subscribe to Events, and the off() method is used to unsubscribe. Most Events APIs have similar event registration and de-registration methods although they may implement them in different ways. If you're crafting your own Events implementation, then it's advisable to ensure that you're providing a familiar set of methods and abstractions. To do this, take inspiration from either the native DOM Events interface or the Node.js Event Emitter. This will ensure that your Events implementation does not surprise or horrify other programmers too much.

Even though an Events API is essentially just a series of callbacks stored and invoked at specific times, there are still challenges in crafting it well. Amongst them are the following:

  • Ensuring the order of invocation when a singular Event fires
  • Handling cases where Events are emitted while other Events are mid-emission
  • Handling cases where Events can be entirely canceled or removed per callback
  • Handling cases where Events can be bubbled, propagated, or delegated (this is usually a DOM challenge)
Propagation, bubbling, and delegation are terms related to firing Events within a hierarchical structure. In the DOM, since <div> may exist within <body>, the Events API has prescribed that, if the user clicks on <div>, the emitted Event will propagate or bubble upward, first triggering any click listeners on <div> and then <body>. Delegation is intentional listening at a higher level of hierarchy, for example, listening at the <body> level and then reacting in a certain way, depending on what the Event object tells you about the Event's target node.

Events provide more possibilities than a simple callback. Since they allow several different Events to be listened for, and the same Event to be listened for several times, any consuming code has far more flexibility in how it constructs its asynchronous control flow. An object that has an Events interface can be passed around throughout a code base and may be subscribed to many times, potentially. The nature of distinct Events, as well, means that different asynchronous concepts or occurrences are usefully kept separated so that a fellow programmer can easily tell which action will be taken in specific circumstances:

const dropdown = new DropDown();
dropdown.on('select', () => { /*...*/ });
dropdown.on('deselect', () => { /*...*/ });
dropdown.on('hover', () => { /*...*/ });

This type of transparent separation helps to encode expectations within the mind of the programmer. It's simple to discern which function will be called in each case. Compare this to a generalized something happened event with an internal switch statement:

// Less transparent & more burdensome:
dropdown.on('action', event => {
switch (event.action) {
case 'select': /*...*/; break;
case 'deselect': /*...*/; break;
// ...
}
});

Well-implemented Events provide a good semantic separation between conceptually different Events and, therefore, provide the programmer with a predictable series of asynchronous actions that they can reason about easily.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.83.150