Revealing constructor

The revealing constructor pattern is a relatively new pattern that is gaining traction in the Node.js community and in JavaScript, especially because it's used within some core libraries such as Promise.

We have already implicitly seen this pattern in Chapter 4, Asynchronous Control Flow Patterns with ES2015 and Beyond, while exploring promises, but let's get back to it and analyze the Promise constructor to embrace it in greater detail:

const promise = new Promise(function (resolve, reject) { 
  // ... 
}); 

As you can see, Promise accepts a function as a constructor argument, which is called the executor function. This function is called by the internal implementation of the Promise constructor and it is used to allow the constructing code to manipulate only a limited part of the internal state of the promise under construction. In other words, it serves as a mechanism to expose the resolve and reject functions so that they can be invoked to change the internal state of the object.

The advantage of this is that only the constructing code has access to resolve and reject and once the promise object is constructed, it can be passed around safely; no other code will be able to call reject or resolve and change the internal state of the promise.

That is exactly the reason why this pattern has been named "revealing constructor" by Domenic Denicola in one of his blog posts.

Note

The full post by Domenic is extremely interesting and also analyzes the historical origins of the pattern and compares some aspects of this pattern with the template pattern used by node streams, or with other construction patterns used by earlier implementation, of the Promise libraries. You can read it here: https://blog.domenic.me/the-revealing-constructor-pattern/.

A read-only event emitter

In this paragraph, we are going to use the revealing constructor pattern to build a read-only event emitter, a special kind of event emitter in which is not possible to call the emit method (apart from within the function passed to the constructor).

Let's write the code for the Roee (read-only event emitter) class in a file called roee.js:

const EventEmitter = require('events'); 
 
module.exports = class Roee extends EventEmitter { 
  constructor (executor) { 
    super(); 
    const emit = this.emit.bind(this); 
    this.emit = undefined; 
    executor(emit); 
  } 
}; 

In this simple class, we are extending the core EventEmitter class and accepting an executor function as the only constructor argument.

Inside the constructor, we invoke the super function to be sure to initialize the event emitter properly by calling its parent constructor, then we save a backup of the emit function and we remove it by assigning undefined to it.

Finally, we call the executor function by passing the emit method backup as argument.

What is important to understand here is that after undefined is assigned to the emit method, it won't be possible to call it anymore from other parts of our code. Our backup version of emit is defined as a local variable that will be forwarded only to the executor function. This mechanism allows us to be able to use emit only within the executor function.

Now let's use this new class to create a simple ticker, a class that emits a tick every second and keeps the count of all the ticks emitted. This will be the content of our new ticker.js module:

const Roee = require('./roee'); 
 
const ticker = new Roee((emit) => { 
  let tickCount = 0; 
  setInterval(() => emit('tick', tickCount++), 1000); 
}); 
 
module.exports = ticker; 

As you can see here, the code is very trivial. We instantiate a new Roee and we pass the logic of event emission within the executor function. Our executor function receives emit as argument so we can use it to emit a new tick event every second.

Now let's see a quick example on how to use this ticker module:

const ticker = require('./ticker'); 
 
ticker.on('tick', (tickCount) => console.log(tickCount, 'TICK')); 
// ticker.emit('something', {}); <-- This will fail 

We use the ticker object the same as any other event emitter-based object, and we can attach any number of listeners with the on method, but in this case, if we try to use the emit method, our code will fail by triggering a TypeError: ticker.emit is not a function error.

Note

Even if this example plays a nice role in showing how the revealing constructor pattern can be used, it is worth mentioning that this read-only functionality for the event emitter is not completely bulletproof and it is still possible to bypass it in several ways. For example, we can still emit an event on our ticker instance by using the original emit prototype directly, as follows: require('events').prototype.emit.call(ticker, 'someEvent', {});

In the wild

Even if this pattern is quite interesting and clever, it is really hard to find common use cases apart from the Promise constructor.

It's worth mentioning that there is a new specification for streams under development that tries to adopt this pattern as a better alternative to the currently used template pattern to be able to describe the behavior of various stream objects: https://streams.spec.whatwg.org.

Also it's important to point out that we already used this pattern in Chapter 5, Coding with Streams when we implemented the ParallelStream class. This class accepts as constructor argument the userTransform function (the executor function).

Even if in this case the executor function is not called at construction time, but in the internal _transform method of the stream, the general concept of the pattern remains valid. In fact, this approach allows us to expose some internals of the stream (for example, the push function) only to the specific logic of the transformation that we want to specify at construction time when creating a new instance of ParallelStream.

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

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