Asynchronous singleton API
After an awesome tech talk with @petermichaux, @racino, @varjs, @qvist, we came to an interesting point, as how to handle a certain situation:
- you need a singleton
- you need it to instantiate lazily
- its instantiation involves querying server-side, thus you only want to do it when it’s absolutely necessary (hence the lazy approach)
- obviously, many parts of the system may be wanting to use what that singleton offers, in any given order, at any given time
What problems does this pose? It is somewhat similar to what is encountered in Java, where threads are involved. You would probably want the getInstance method to be synchronized. This is to some extent also true in case of the lazy, asynchronous instantiation in JavaScript. Because what happens, when getInstance is called multiple times, before instantiation is finished? Bad, unpredictable things.
This could seem as an obvious call for the deferred approach. But what got cooked rather resembled the state machine. So, enough talking, let’s code!
Passing the singleton object instance
We cannot get the singleton and assign it immediately, like so:
var mySingletonInstance = MySingleton.getInstance();
since it might not return anything at that point. We need to do it using callbacks passed to the instantiation logic, which get called when the job gets done. So we would rather go like this when using the singleton:
MySingleton.getInstance({
success : function(singletonInstance) {
// here we get access to our desired instance
},
failure : function(error) {
// bugger, something went wrong
}
});
Concurrent singleton instance access
All cool, but we still need to handle the case where multiple other parts of the system request access to the instance before it gets created. Like this:
// in module foo
MySingleton.getInstance({
success : function(singletonInstance) {
// do something useful with what the server provided
},
failure : function(error) {
// bugger, something went wrong
}
});
// in module bar
MySingleton.getInstance({
success : function(singletonInstance) {
// do something just as useful
},
failure : function(error) {
// bugger, something went wrong
}
});
Callback queue
What we need be doing, is collect all the callbacks, and execute them once the object is ready to be used. So let’s build up a skeleton of the solution:
var MySingleton = {};
(function() {
var callbackQueue = [];
var instance = null;
var doRequests = function(callbacks) {
//code for interaction with the server
};
MySingleton.getInstance = function(callbacks) {
}
}());
States
Let’s think about what state our singleton can be in. One is before instantiation, which you could call before. When it’s called for its instance, it could be working. Once all is done it could be success, and if something went wrong, failure. Extending our skeleton with the states:
var MySingleton = {};
(function() {
var state = "before";
var callbackQueue = [];
var instance = null;
var doRequests = function(callbacks) {
//code for interaction with the server
};
MySingleton.getInstance = function(callbacks) {
switch (state) {
case "before":
//this would be the case of the first call for the instance; here we start instantiating and queue up the first callback
state = "working";
callbackQueue.push(callbacks);
break;
case "working":
//here we just queue up further callbacks
case "success":
//in this case we execute the success callback immediately
case "failure":
//like in success, but we pass some custom error object to the failure callback
default:
throw new Error("Invalid state: " + state)
}
};
}());
Asynchronous instantiation
One extra thing we’ve got to remember is that the instantiation itself, due to its asynchronous nature, also happens with the help of callbacks, hence the extra handling of them in the before branch:
var MySingleton = {};
(function() {
var state = "before";
var callbackQueue = [];
var myInstance = null;
var error = null;
var doRequests = function(callbacks) {
//code for interaction with the server
};
MySingleton.getInstance = function(callbacks) {
switch (state) {
case "before":
state = "working";
callbackQueue.push(callbacks);
doRequests({
success : function(singletonInstance) {
state = "success";
myInstance = singletonInstance;
},
failure : function(errorObj) {
state = "failure";
error = errorObj;
}
});
break;
case "working":
//here we just queue up further callbacks
case "success":
//in this case we execute the callback immediately
case "failure":
//like in success, but we pass some custom error object
default:
throw new Error("Invalid state: " + state)
}
};
}());
Resolving callback queue
Now let’s take care of resolving the callback queue. Throughout this article, I’ve been assuming the callbacks were passed in form of an object like:
MySingleton.getInstance({
success : function(singletonInstance) {
},
failure : function(error) {
}
});
So here goes:
var MySingleton = {};
(function() {
var state = "before";
var callbackQueue = [];
var myInstance = null;
var error = null;
var doRequests = function(callbacks) {
//code for interaction with the server
};
MySingleton.getInstance = function(callbacks) {
switch (state) {
case "before":
state = "working";
callbackQueue.push(callbacks);
doRequests({
success : function(singletonInstance) {
state = "success";
myInstance = singletonInstance;
for (var i = 0, ilen = callbackQueue.length; i < ilen; i++) {
callbackQueue[i].success(myInstance);
}
callbackQueue = [];
},
failure : function(errorObj) {
state = "failure";
error = errorObj;
for (var i = 0, ilen = callbackQueue.length; i < ilen; i++) {
callbackQueue[i].failure(error);
}
callbackQueue = [];
}
});
break;
case "working":
//here we just queue up further callbacks
case "success":
//in this case we execute the callback immediately
case "failure":
//like in success, but we pass some custom error object
default:
throw new Error("Invalid state: " + state)
}
};
}());
Final solution
Let’s build the rest of the state branches:
var MySingleton = {};
(function() {
var state = "before";
var callbackQueue = [];
var myInstance = null;
var error = null;
var doRequests = function(callbacks) {
//code for interaction with the server
};
MySingleton.getInstance = function(callbacks) {
switch (state) {
case "before":
state = "working";
callbackQueue.push(callbacks);
doRequests({
success : function(singletonInstance) {
state = "success";
myInstance = singletonInstance;
for (var i = 0, ilen = callbackQueue.length; i < ilen; i++) {
callbackQueue[i].success(myInstance);
}
callbackQueue = [];
},
failure : function(errorObj) {
state = "failure";
error = errorObj;
for (var i = 0, ilen = callbackQueue.length; i < ilen; i++) {
callbackQueue[i].failure(error);
}
callbackQueue = [];
}
});
break;
case "working":
callbackQueue.push(callbacks);
break;
case "success":
callbacks.success(myInstance);
break;
case "failure":
callbacks.failure(error);
break;
default:
throw new Error("Invalid state: " + state)
}
};
}());
We’re done!
And that’s about it! Further improvements may include making MySingleton and its getInstance protected against overwriting. To achieve this, we could use ES5 object and property descriptors, some of which I’ve already covered.

As a side note deferred/promise concept is also perfect for that, in ES5 you can setup it very neatly with help of getters:
(function () { var promise; var doRequests = function () { // ... initialize work and setup promise return promise; }; Object.defineProperty(MySingleton, 'getInstance', { get: function () { return promise || doRequests(); } }); }());In ES3 you can easily workaround getter for that case, so it’s not much hassle either:
MySingleton.getInstance = function () { if (!promise) { doRequests(); } return promise.apply(this, arguments); };With above you call getInstance following way:
MySingleton.getInstance(onsuccess, onerror);
Oh yes, I like the above a lot :) you get a really clean promise-based API, and it’s so simple and straightforward.