It all started with the promise (pun intended) of asynchronous code.

We’d been writing callbacked Javascript since what seemed like the beginning of time. For better or worst, it’s got us through the JavaScript stone age, but with the golden age now upon us, it’s more than time to look at saner approaches to asynchronous web development using more advanced design patterns.

Callbacks – the highway to hell

Chances are that if you’ve worked with JavaScript, you’ve used callbacks. If that’s the case feel free to jump to Promises.

Just in case you haven’t or you need a memory refresh, here’s what they look like:

function doSomething(callback) {

function doSomethingElse() {

doSomething(function() {    // logs: '0'
    console.log('0');    // logs: '1'

doSomething(    // logs: '0'
    doSomethingElse    // logs: '1'

The first time we call doSomething, it calls the anonymous function we passed as an argument . The second time it calls the doSomethingElse function.

Let’s take a look at a real world example:

// void log(message, callback, errback)
// void sendDataToClient(testClient, data, errorCode, callback, errback)
// void getDataFromServer(testServer, callback, errback)

    function(data) { // getDataFromServer callback
        log('Data from server', data,
            function(){ // Log callback
                sendDataToClient(null, data, 0,
                    function(message){ // sendDataToClient callback
                        log('Message from client', message);
                    function(error) { // sendDataToClient errback
                        log('Error from client', error);
    function(error) { // getDataFromServer callback
        log('Error from server', error);

It’s okay if you’re feeling nauseous, we call it Callback Hell for a reason. The simplest way to prevent a code pyramid is to store our callbacks within variables.

var afterGetDataFromServerSuccess = function(data) {
    log('Data from server', data, afterLogDataFromServerSuccess);

var afterGetDataFromServerError = function(error) {
    log('Error from server', error);

var afterLogDataFromServerSuccess = function(data) {
    sendDataToClient(null, data, 0, afterSendDataToClientSuccess, afterSendDataToClientError);

var afterSendDataToClientSuccess = function(message) {
    log('Message from client', message);

var afterSendDataToClientError = function(error) {
    log('Error from client', error);


Already more readable/cleaner isn’t it?

We still have a couple of problems tho:

  • we need to write a lot of code (don’t forget programmers are lazy)
  • we’re clogging up our arguments with callbacks

Promises – we’ve been waiting for you

Promises are not natively supported on ES5 or IE but bluebird fixes all of that for you.

They can be looked at like an abstraction on top of callbacks.

// Promise getData()
function doSomething() {
    return new Promise(function(resolve, reject){
        if (somethingPossiblyTrue) {
            return resolve('This is the result');
        } else {
            return reject('This is the error/rejection');

function doSomethingAlternateSyntax() {
    if (somethingPossiblyTrue) {
        return Promise.resolve('This is the result');
    } else {
        return Promise.reject('This is the error/rejection');

doSomething().then(function(result) {
 // Executes on resolve
}).catch(function(error) {
 // Executes when there an error/rejection

As you can see, the syntax is quite explicit, although a lot of code is involved. One of the perks of Promises is that it’s easy to have the promise returned through the several functions.

// Promise DB.getUser(userId)
// Promise SessionStorage.getCurrentUserId()

function getUserById(userId) {
    return DB.getUser(userID);

    .then(getUserById) // gets the userId from getCurrentUserId
    .then(function(user) {
        console.log('User', user);
    .catch(function(error) {
        console.log('Error', error);

You can chain methods using thens if the required arguments for the following function are returned through resolve.

There is, however, a problem here. Errors are implicitly silenced, the usual try…catch cannot be used; they need to be caught through a Promise’s catch function.

This is about as good as it gets while keeping the asynchronous code flow.

Observer Pattern – decoupling is good

JavaScript was built around events, it gives use an easy way to decouple our codes when we use said events.

function doSomethingOnClick() {
    // do something

    .addEventListener("click", doSomethingOnClick, false);

In our above example, doSomethingOnClick (the Subject) is not aware of the click event. This is the logic behind the Observer/Subject pattern. The following diagram illustrates this perfectly:

Observer subject

The Implementation

Since most Observer/Subject pattern implementations are quite extensive, I’ve written a simple implementation to showcase its strengths.

// Simplifies referencing the observer instances
global.objectId = (function () {
    var objects = [];

    return function(obj) {
        if (objects.indexOf(obj) === -1) {
        return objects.indexOf(obj);

var Subject = {
    events: {},
    observers: {},
    addEvent: function(eventName, methodName) {[eventName] = methodName;
        this.observers[eventName] = {};
        return this;
    removeEvent: function(eventName) {
        delete this.observers[eventName];
        return this;
    addObserver: function(event, observer) {
        this.observers[event][] = observer;
        return this;
    removeObserver: function(event, observer) {
        delete this.observers[event][];
        return this;
    trigger: function(event, data) {
        var observer = null;

        for (var observerId in this.observers[event]) {
            observer = this.observers[event][observerId];
        return this;

var Observer = function(name){ = name;

Observer.prototype = {
    id: function() {
        return objectId(this);
    name: function() {

We can now create events through the Subject and add Observers to them.

Let’s observe what happens when we test our implementation out:

var bird = new Observer('Bird');
bird.tryToSurvive = function(disasterName) {
    console.log(' ' + ' flew away from a '+disasterName+'!');

var dog = new Observer('Dog');
dog.tryToSurvive = function(disasterName) {
    console.log(' ' + ' hid from the '+disasterName+'!');

Subject.addEvent('disaster', 'tryToSurvive')
 .trigger('disaster', 'Meteor Strike') // Nothing happens
 .addObserver('disaster', bird)
 .trigger('disaster', 'Meteor Strike');// logs: Bird ... Meteor Strike

Subject.addObserver('disaster', dog);

setInterval(function() {
    Subject.trigger('disaster', 'Tsunami');
    // logs: Bird ... Tsunami
    // logs: Dog ... Tsunami
}, 500);

Although there are a few different forks of the Observer/Subject pattern, the most notorious is probably the Publisher/Subscriber pattern which is used by the Flux architecture for the React library.

The only major difference between Observer/Subject and Pub/Sub is that in the latter, the Publishers (Observer-like) does not know about the Subscribers. There’s a mediator (or broker) that sends the messages from the Pubs to the Subs, allowing for better decoupling on both sides.

pub sub

Like we’ve seen the Observer/Subject pattern is extremely powerful for managing asynchronous applications, but does require a certain amount abstraction to be properly implemented.

For small/simple projects we do have a better solution.

Async/Await – nobody wants an async flow

Async/Await is a feature proposed for ES7, it adds asynchronous functions to JavaScript. If you want to use it with your project you’ll have to use the Babel transpiler.

// Promise getData()

function getJsonData() {
    return getData().then(function(result) {
            return Promise.resolve(JSON.parse(result));
        }).catch(function() {
            return Promise.reject(null);
async function retrieveDataAndLog() {
    var data = await getJsonData();
    console.log(data); // This is the third log to run

console.log(1); // This is the first log to run
console.log(2); // This is the second log to run

As you can see, we still might need to use Promises but at least we can remove them from the external layers of our code, simplifying the higher level flow.

Decorators – gift wrapping for your code

Native decorators are also a brand new feature in ES7 but there are working implementations in ES5. They are essentially just wrappers for components that allow you to add functionalities with a more elegant syntax than a non-DRY approach.


Here’s a simple ES5 implementation:

// Function Function.clone()

var add = function(n1, n2) {
    return n1 + n2;

var sub = function(n1, n2) {
    return n1 - n2;

function decoratorAddOne(func) {
    var oldFunc = func.clone();
    return function() {
        return oldFunc.apply(this, arguments) + 1;

add = decoratorAddOne(add); // Wraps decorator around add
sub = decoratorAddOne(sub); // Wraps decorator around add

console.log(add(1, 1)); // Gives 3: (1 + 1) + 1
console.log(sub(5, 2)); // Gives 4: (5 - 2) + 1

This pattern is especially practical to reduce the amount of code needed when defining several methods with the same functionalities.

Other patterns

Most, if not all Object-Oriented patterns can be implemented in JavaScript. This is made easy with ES6 since it supports classes. The ones most commonly used are:

  • The Facade pattern: Provides an opaque interface to classes/object, it is commonly used for simple decoupling

  • The Singleton pattern: Instead of creating instances, the Singleton pattern advocates using a single object. This is commonly used to help reduce the size of the object pool and the memory usage.

  • The Factory pattern: Methods on static classes/objects are used to generate instances of objects. This is commonly used to implement a mid-level API. For example, AngularJS 1.X uses this extensively.

Although some of these patterns are extremely powerful when used properly, we still have to be careful when designing/implementing our application architecture. Asynchronous coding is far from trivial. We’ve already moved away from the shadows of Callback Hell, but there’s still work to do to make sure we never go back.