justjs: node.js tutorials

New here? You might want to start at the beginning.

12/30 '12

Singletons in #node.js modules cannot be trusted, or why you can't just do var foo = require('baz').init()

Today I realized that singletons in node.js modules cannot be trusted in a project in which several npm modules depend on each other.


Most Node developers have figured out that if two files in your project require the same module, like this:
 
one.js:
var shared = require('shared');

two.js:
var shared = require('shared');
 
... Then they will get back the same object, because Node caches requests for the same module.
 
But you might not realize (I sure didn't!) that if your project is based on npm modules that both require the "shared" module as a dependency, like this:

node_modules/one/index.js: 
var shared = require('shared'); 

node_modules/two/index.js: 
var shared = require('shared');
... And you install their dependencies with npm, there will be two copies of "shared/index.js" hanging about:
 
node_modules/one/node_modules/shared/index.js
node_modules/two/node_modules/shared/index.js
 
The cache mechanism of "require" does not consider these to be the same file. In my tests, the two modules get different objects back from require('shared').
 
Yes, there's a way around this. If your main project also requires "shared," and that copy is installed with npm first before either one or two is installed with npm, and they depend on the same version, then npm will be clever and install the module only once at the main project's level, allowing the require statements in one/index.js and two/index.js to search upwards in the tree until they find the same file and return the same object.
 
But you'll note there are an awful lot of conditions and provisos in the above paragraph. It's very magical, and things that are magical tend to go wrong in mysterious ways.
 
This may be one reason why Express, to take a popular npm module as an example, returns a constructor for creating apps rather than just returning an app directly when you require('express'). This forces you to construct an app object and then explicitly pass it to other modules that need it. There is no temptation to rely on the above dodgy, sometimes-not-really-there mechanism to share the same singletons.
 
There are other reasons to avoid singletons of course. Instances aren't that hard to keep track of, and as long as your code sticks to constructing instances when asked for them and avoids returning singletons, you will never have the "gee, I wish I'd written this module to support more than one instance in a project" conversation. Which can be very unpleasant.
 
Here's a simple example, taken from my uploadfs module, of an npm module that does this the right way:
 
// Export a convenience function that creates an instance
module.exports = function() {
  return new uploadfs();
}

// The actual constructor function, which is also a closure 
// holding all the private state information

function uploadfs() {
  // Reference to "this" that won't get clobbered by some other "this"
  var self = this;
  // Private state variables
  var tempPath;
  var backend;
  var imageSizes;
  var orientOriginals = true;

  // Public method: initialize the object
  self.init = function(options, callback) { ... }

  // Private functions can exist in the closure too; they can see the state but
  // outsiders can't touch them

  function private() { ... }
}

 

blog comments powered by Disqus