1

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

Alexander Mills
  • 90,741
  • 139
  • 482
  • 817
  • at Trott, I saw your answer, and I was writing a comment - the code you provided won't work because it will end up being user defined code, not mine, and I can't control whether they will implement the singleton stuff correctly. So I need to do some clever behind the scenes stuff, like monkeypatching require function or something. Or keeping some hash of loaded modules, I am not sure. – Alexander Mills Mar 19 '17 at 06:05
  • Is it only your library calling the exported function you want to control? or any code calling the function? – Matt Mar 19 '17 at 06:37
  • My code is the only one that invokes this module, so the answer to your first question is yes, but the source code of the module, while having to adhere to a certain interface*, isn't completely under my control – Alexander Mills Mar 19 '17 at 06:42
  • the *interface, of course, is it must export a function which returns an object – Alexander Mills Mar 19 '17 at 06:43
  • What's the interface for your library to receive the users function/module? – Matt Mar 19 '17 at 07:50
  • I added it, I already had some of that info, but I went into more detail - it would be cool to develop a nifty solution to this but I am not sure how possible it is. Perhaps I can retrieve the module from the require cache and then overwrite the module.exports value, but that is definitely getting into tricky territory. – Alexander Mills Mar 19 '17 at 08:04

1 Answers1

1

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...
Matt
  • 68,711
  • 7
  • 155
  • 158
  • agree that it's a good idea to avoid internals/monkeypatching, and I am ok with properties on functions, but just looking for something a little slicker and foolproof. – Alexander Mills Mar 19 '17 at 08:06
  • yeah that proxy thing sounds interesting, that's more of what I am going for I think – Alexander Mills Mar 19 '17 at 08:08
  • I literally just need to monkeypatch the user-defined function with my own, AFAICT, but I am not sure how to do that right, it needs to work globally throughout my program, and adhere to the rules of JS – Alexander Mills Mar 19 '17 at 08:10
  • Added a proxy example. You could make a plain function mimic the Proxy more closely than the top example too. – Matt Mar 19 '17 at 09:03
  • cool, will give it a shot tomorrow to see if I can get it to work, if it works will consider accepting the answer – Alexander Mills Mar 19 '17 at 09:52
  • haven't tried it yet, but it's pretty much what I am looking for an am sure will work – Alexander Mills Mar 20 '17 at 18:53