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
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...