Skip to main content
Ben Nadel at CF Summit West 2024 (Las Vegas) with: Brian White
Ben Nadel at CF Summit West 2024 (Las Vegas) with: Brian White

Using Method Chaining With The Revealing Module Pattern In JavaScript

By
Published in Comments (17)

Yesterday, I looked at creating null-prototype objects in Node.js. As part of that exploration, I created a very simple cache class with public methods that could be chained. However, since the public methods were exposed using the revealing module pattern, it dawned on me that the "this" reference works; but, it works in a very interesting way. When using method chaining, in conjunction with the revealing module pattern, you can only use "this" to refer to public methods.

To explain this in better detail, I've re-written my simple cache class to make the syntax a little more obvious:

// Create an instance of our cache and set some keys. Notice that the [new] operator
// is optional since the SimpleCache (and revealing module pattern) doesn't use
// prototypical inheritance. And, we can use method-chaining to set the cache keys.
var cache = SimpleCache()
	.set( "foo", "Bar" )
	.set( "hello", "world" )
	.set( "beep", "boop" )
;

console.log( cache.has( "beep" ) );


// ---------------------------------------------------------- //
// ---------------------------------------------------------- //


// I provide a super simple cache container.
function SimpleCache() {

	// Create an object without a prototype so that we don't run into any cache-key
	// conflicts with native Object.prototype properties.
	var cache = Object.create( null );

	// Reveal the public API.
	return({
		get: get,
		has: has,
		remove: remove,
		set: set
	});


	// ---
	// PUBLIC METHODS.
	// ---


	// I get the value cached at the given key; or, undefined.
	function get( key ) {

		return( cache[ key ] );

	}


	// I check to see if the given key has a cached value.
	function has( key ) {

		return( key in cache );

	}


	// I remove the given key (and associated value) from the cache.
	// --
	// NOTE: Returns [this] for method chaining.
	function remove( key ) {

		delete( cache[ key ] );

		// CAUTION: In this context, [this] does not refer to the SimpleCache instance;
		// rather, it refers to the public API that was "revealed". As such, method
		// chaining can only work in conjunction with "public" methods.
		return( this );

	}


	// I cache the given value at the given key.
	// --
	// NOTE: Returns [this] for method chaining.
	function set( key, value ) {

		cache[ key ] = value;

		// CAUTION: In this context, [this] does not refer to the SimpleCache instance;
		// rather, it refers to the public API that was "revealed". As such, method
		// chaining can only work in conjunction with "public" methods.
		return( this );

	}

}

Notice that the two methods, .remove() and .set(), both return "this". In a typical "prototypal context," you might expect "this" to refer to the instance of SimpleCache. However, since we're using the revealing module pattern, there may not be an "instance" of SimpleCache - at least not in the traditional sense. In this case, since we're "revealing" a public API by returning a completely new object literal, the "this" will refer specifically to the object literal, aka, our public API.

Using method chaining with the revealing module pattern in JavaScript.

While I don't have any private methods in this example, I hope you can see that method chaining won't work with private methods. In the revealing module pattern, private methods (and variables) are only available due to the lexical binding of the SimpleCache() function which "closes over" the function references. As such, if I attempted to return "this" from a private function, I would lose access to everything but the public API.

That said, I would assume that the vast majority of use-cases for method chaining sit with public methods rather than with private methods. As such, I don't really consider this a problem. But, if you're going to be combining the revealing module patterns with method chaining, you really are getting knee-deep into the wonderfully flexible world of JavaScript! Proceed with much joy and some caution!

Want to use code from this post? Check out the license.

Reader Comments

1 Comments

nice post. however, a disadvantage of revealing module pattern is that it is not as memory-efficient as directly using prototype pattern. Everytime the cache instance is created, a fresh copy of get/set methods is created.

3 Comments

You could stop it creating separate instances of the get/set methods by caching them in a one-shot closure :

var SimpleCache = (function(){
return function SimpleCache() {
var cache = Object.create( null );

// Reveal the public API.
return({
get: get,
has: has,
remove: remove,
set: set
});
};

function get( key ) {
... }

function has( key ) {
... }

...

})();

15,841 Comments

@Varun,

That's definitely true; but, you also have to take some perspective on the fact that memory is relatively cheap these days. Also, if you are dealing with singletons, the difference won't matter as there is only ever one instance of a given function.

That said, if you have to instantiate a lot of these things, you are totally right. Also, using the prototype (instead of revealing module pattern) lends well to prototypal inheritance which is used very widely in contexts like Node.js. If you look at many of the core Node.js modules, like EventEmitter and Streams, they all use prototype-based function definitions so that they can be efficient and easily subclassed.

So, excellent point! Thanks.

15,841 Comments

@Imma,

Sorry about the formatting :( That's definitely one of the biggest Achilles' heels of this blog :(

That said, you have to be careful about caching the get/set functions as you might lose association with the correct cache. The point of the revealing module pattern is that all the variables and methods live in single "container" formed by the closure. This is how they know to reference each other. If you pull the methods out of the contain, I fear that they will no longer know how to reach each other.

If you want to do that, it might be best to fallback to using prototype-based inheritance where the functions are defined on the prototype, rather than in the closure. Of course, if you do this, you no longer have "private" variables, which is why prototype-based contexts usually adopt the convention of putting "_" before private variables. As in:

this.amPublic = "something";
this._amPrivate = "something_else";

This way, both the public and "private" scopes can be "this"; and, the private variables are kept private by convention.

12 Comments

This is not the revealing module pattern. It's a factory function. The revealing module pattern exports its public API by tacking it onto a global object, and is generally an immediately invoked function expression (iife).

In the case you show, the public API combined with the variables closed over *is* the instance, because you're returning an arbitrary object instead of one created by `new`. Other than the identity of `this` and the fact that the public API is not a reference to `constructor.prototype`, everything else re: access to private funcs and variables is exactly the same as it would be in a normal constructor.

In other words, even if you had used a constructor function, you would still not have access to the private variable, `cache` using `this`.

12 Comments

A couple more points worth mentioning:

Now that JavaScript has standard modules with ES6, along with the very popular node module format, the revealing module pattern is an anti-pattern. Real modules should be used, instead.

To clear up more misconceptions about object and inheritance patterns in JavaScript, I wrote a post called "Common Misconceptions About Inheritance in JavaScript" - https://medium.com/javascript-scene/common-misconceptions-about-inheritance-in-javascript-d5d9bab29b0a

You might like it. =)

15,841 Comments

@Eric,

In all fairness, a lot of the examples of the revealing module pattern seem to follow this approach; so, this might be a byproduct of misconceptions gone wild :( That said, I am not sure there is a real difference between a factory function and an IIFE - both of them are functions that [often] return something. But, I didn't realize that the globalness of the revealed components was part of the definition of the revealing module patterns. If that were / is the case, then I would agree that it is an anti-pattern - pretty much anything with the word "global" in it is a anti-pattern these days.

When you bring up Node, however, I think it's good to point out that there is no "one size fits all." I have been learning Node for a bit now, and my format of module definitely changes depending on what I am doing. In Node.js, I tend to use more Prototype-oriented object definitions because that seems to be more of the main-stream approach, especially for anything that needs to be extended (which in Node, it seems like everything inherits from EventEmitter :D).

You start throwing some Promises and EventEmitters into the mix and now you are back in "this" hell. Even with things like .bind(), you can get in trouble. For example, in the event-handler of an Stream, the "this" context is often the Stream instance, which is critical when you need to do things like call "this.end()" after an error event breaks a pipe.

I'll caveat that heavily with the fact that I am very new to Node.js, so I'm still finding my own personal style.

I'll check out the article, thanks for the link!

15,841 Comments

@Eric,

Really solid article! Almost too much good information to absorb in one pass.

Your discussion of multi-prototype assign() got me thinking about Node.js. To be honest, thinking about inheriting behaviors from multiple sources is not something I am very good at thinking about. I popped over to the Transform / Duplex streams in Node to see what they do - since they exhibit both Readable and Writable behavior. It's actually quite an interesting class to look at:

https://github.com/joyent/node/blob/master/lib/_stream_duplex.js

It extends Readable and then selectively copies over properties from Writable that won't overwrite Readable properties. But, then, it calls the "super constructor" for each class:

Readable.call( this );
Writable.call( this );

... within the constructor function of the Duplicate stream.

I see stuff like this and I think, "I'm way out of my element here."

When I have some more time, I'm gonna go back and read your article again.

3 Comments

I think i can clear this bit up : "That said, I am not sure there is a real difference between a factory function and an IIFE"
The difference is the II of the IIFE - a factory function is invoked many times to create instances, and IIFE is immediately invoked once to produce a single instance - the module bit (of the revealing module pattern)

hope that helps :-)

12 Comments

@Ben,

I did a webcast about modules that you may appreciate. I typically export a single function which is either a factory function that takes an options object and returns an instance, or a pure function that takes its required inputs.

https://vimeo.com/89258863

See Chapter 2 in Programming JavaScript Applications for more on function purity: http://pjabook.com

When I need to compose multiple things together, I use stamps: http://ericleads.com/2013/02/fluent-javascript-three-different-kinds-of-prototypal-oo/

It's funny you mention duplex streams. That's my go-to case to show how awkward multiple inheritance can be when you use a class-based approach.

The author chose to use two forms of inheritance out of habit -- they use a pseudo-class inheritance utility called `util.inherits` which only takes a single ancestor, and to inherit from the second ancestor, they had to combine concatenative and functional inheritance in ad-hoc fashion instead of consistently using the same type of inheritance everywhere.

That actually makes it awkward enough to inherit from duplex that there are many utilities in the node ecosystem which exist just to make it easier to inherit from duplex streams.

Contrast with stamps:

duplex = stampit.compose(readable, writable);

customThrough = stampit.compose(duplex, customBehavior);

myInstance = customThrough();

A consistent, easy api for both inheritance and instantiation. Want to inherit from n objects? Make a single function call. Want an instance? Make a normal function call. Everything has the same simple API that everybody already knows: Just a normal function.

Functions in JavaScript are at once the most simple and the most powerful construct available in the language. In JavaScript, functions do everything that classes can do, but do it better, and as you get better and better at writing, using, and composing functions, you'll learn how to also make them the most flexible construct in the language.

I'm still trying to figure out simple ways to describe the simple power of functional programming, but here's a start: https://medium.com/javascript-scene/the-two-pillars-of-javascript-pt-2-functional-programming-a63aa53a41a4

12 Comments

@Ben,

> this might be a byproduct of misconceptions gone wild

Indeed.

> I am not sure there is a real difference between a factory function and an IIFE

A factory function is just a normal function which returns an object. If it returned anything else, it would not be a factory function anymore, by definition. Factory functions can be, but typically are not immediately invoked. In the _rare_ case that it is immediately invoked, it is both a factory and an IIFE.

An IIFE is a function that is immediately invoked. It can return anything, including primitives, or (commonly) nothing at all. Typically, IIFEs share very little in common with factory functions other than the fact that they're both functions.

The _revealing module pattern_ always exports an API by attaching it to some pre-existing object -- usually global, but in some cases, to an application object, or an object injected in the parameters. The earliest versions of the module pattern attached themselves by assignment to a variable in the outer scope (almost certainly the global object) as in this gist: https://gist.github.com/ericelliott/058c7f4df5d28254687e

That form was later abandoned for versions that passed the global object into the IIFE as an argument when people started writing isomorphic JS apps circa 2010 so that modules would work correctly in Node as well as browsers, because in Node, all variables defined in a file are local by default, not global. The original form of the module pattern didn't work in Node.

The reason that attaching to a pre-defined object was required is because in those pre-module standard days, the outer scope in any file *was* the global scope. The original modern pattern exists to insulate its contents from polluting the global scope with all of its internal state.

That is not the case in modern JavaScript, because today, we use node-style modules, ES6 modules, or (if you're falling behind the progress curve) AMD modules.

In other words, (with the exception of AMD) the file itself limits the scope of the module's internal state.

It's possible that some people mistake exporting a function while encapsulating private state is the revealing module pattern. That's incorrect. That is the definition of a _closure_.

Closures are described in more depth in Chapter 2 of Programming JavaScript Applications. http://pjabook.com

Some variations of the module pattern are described in Chapter 4.

12 Comments

One more quick note: All versions of the module pattern are now obsolete.

Today, people should be using ES6 or node-style modules. Some stragglers are still using AMD modules, but the AMD format has no future, and should be avoided.

15,841 Comments

@Eric,

Thanks for the video link and "stamps". I've never even heard of that - I'll have to take a look. And, I think it's time I go back and read your book. The first time I tried to read it, it was in O'Reilly's "early release" program, which meant that it was like 10% done :| I never took the time to go back and read it in it's complete format.

And, form what you're saying here, I can only imagine that it's an awesome book.

For the last 3 years, I've been living in the AngularJS ecosystem; so, it's basically been nothing but factory functions (so to speak) for me. I haven't even really looked at AMD since I started Angular's dependency injection (and concatenated JS files).

But, now that I am diving into Node.js, I'm getting more familiar with the node module system. Hopefully that will paint a new perspective for me.

Thanks! Can't wait to get back to your book and give it a revamped review.

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel