What is the simplest/cleanest way to implement singleton pattern in JavaScript?
Because non-blocking nature of JavaScript, Singletons in JavaScript are really ugly in use. Global variables will give you one instance through whole application too without all these callbacks, module pattern gently hides internals behind the interface. See @CMS answer.
But, since you wanted a singleton…
var singleton = function(initializer) {
var state = 'initial';
var instance;
var queue = [];
var instanceReady = function(createdInstance) {
state = 'ready';
instance = createdInstance;
while (callback = queue.shift()) {
callback(instance);
}
};
return function(callback) {
if (state === 'initial') {
state = 'waiting';
queue.push(callback);
initializer(instanceReady);
} else if (state === 'waiting') {
queue.push(callback);
} else {
callback(instance);
}
};
};
Usage:
var singletonInitializer = function(instanceReady) {
var preparedObject = {property: 'value'};
// calling instanceReady notifies singleton that instance is ready to use
instanceReady(preparedObject);
}
var s = singleton(singletonInitializer);
// get instance and use it
s(function(instance) {
instance.doSomething();
});
Singletons give you more than just one instance through whole application: their initialization is delayed till first use. This is really big thing when you deal with objects whose initialization is expensive. Expensive usually means I/O and in JavaScript I/O always mean callbacks.
Don't trust answers which give you interface like instance = singleton.getInstance(), they all miss the point.
If they don't take callback to be run when instance is ready, then they won't work when initializer does I/O.
Yeah, callbacks always look uglier than function call which immediately returns object instance. But again: when you do I/O, callbacks are obligatory. If you don't want to do any I/O, then instantiation is cheap enough to do it at program start.
var simpleInitializer = function(instanceReady) {
console.log("Initializer started");
instanceReady({property: "initial value"});
}
var simple = singleton(simpleInitializer);
console.log("Tests started. Singleton instance should not be initalized yet.");
simple(function(inst) {
console.log("Access 1");
console.log("Current property value: " + inst.property);
console.log("Let's reassign this property");
inst.property = "new value";
});
simple(function(inst) {
console.log("Access 2");
console.log("Current property value: " + inst.property);
});
In this example setTimeout fakes some expensive I/O operation. This illustrates why singletons in JavaScript really need callbacks.
var heavyInitializer = function(instanceReady) {
console.log("Initializer started");
var onTimeout = function() {
console.log("Initializer did his heavy work");
instanceReady({property: "initial value"});
};
setTimeout(onTimeout, 500);
};
var heavy = singleton(heavyInitializer);
console.log("In this example we will be trying");
console.log("to access singleton twice before it finishes initialization.");
heavy(function(inst) {
console.log("Access 1");
console.log("Current property value: " + inst.property);
console.log("Let's reassign this property");
inst.property = "new value";
});
heavy(function(inst) {
console.log("Access 2. You can see callbacks order is preserved.");
console.log("Current property value: " + inst.property);
});
console.log("We made it to the end of the file. Instance is not ready yet.");