Search code examples
node.jsdesign-patternssingletonsingleton-methods

Throw error if exported module function is called twice


I have a Node.js module that exports a function:

module.exports = function(data){

   return {
     // return some object
    }

};

I am looking to use a singleton pattern here, but without much extra fuss. This is for a library, and it's possible that my library code might call this function more than once by accident, but I need to rely on the user to define the code, so I can't guarantee that they will implement a proper singleton pattern.

Is there a good pattern I could use to throw an error if the exported function is called more than once?

I am thinking something like this:

const fn = require('./user-defined-module');
const someObject = fn();
// next I want to somehow mark this module as having been loaded

To be explicit, the following is not good:

var loaded = null;

module.exports = function(data){

  if(loaded) {
    return loaded;
  }

  // do some stuff

  return loaded = {
    // return some object
  }

};

for some use cases that might suffice, but I am looking to do away with the standard singleton code for several reasons.

I don't think monkeypatching the require function will help with this but maybe.

Right now my code to load such a module, is like so:

const fn = require('./user-defined-module');
const obj = fn.apply(ctx, some_args);

the problem is that somewhere else in my codebase I could call

const obj = fn.apply(ctx, some_args);

again. The codebase is getting big, and I would like to fail fast.

In my mind, the best thing to do would be to redefine fn, once it's called the first time.

(1) I would have to set the local variable fn to something like

fn = function(){
  throw 'whoops you messed up'
}

and more importantly (2)

I would have to set the module.exports value to

module.exports = function(){
  throw 'whoops you messed up'
};

hope that makes some sense


Solution

  • Attach some data to the function(s) when passed to your library or when the function is called by your library for the first time. Then guard your libraries calls to the method with that property:

    function callUserFn() {
      let fn = require('./user-defined-module');
      if ( !fn._calledByMyLibrary ) {
        fn._calledByMyLibrary = true
        return fn()
      } else {
        throw new Error('Already called')
      }
    }
    

    Properties on functions are pretty rare so it's unlikely to collide with anything preexisting and it avoids messing with Node internals.

    Replacing your libraries reference to the function with a Proxy for the function could achieve the same thing on newer Node environments. The called data could then be set in the Proxy rather than on the function.

    let fn = new Proxy(require('./user-defined-module'), {
      apply(target, that, args){
        if ( fn.called === true ) throw new Error('Already called')
        fn.called = true
        return target.apply(that, args)
      }
    })
    
    fn('test','one')
    fn('test','two') // => Error: Already called...