Singletons in JavaScript

How to implement them and whether they’re actually useful

Published on
Jul 26, 2020

Read time
3 min read

Introduction

A singleton is a function or class which can have only one instance. It’s a design pattern popularised by the “Gang of Four” in their influential Design Patterns. However, the singleton is one of several patterns in the book which have been criticised. Some are regarded as anti-patterns, while others are only useful in particular languages. So, what about singletons? And do they make sense JavaScript?

In this article, we’ll see how to implement singletons in JavaScript, before asking whether or not they’re useful in the language.

Class Singleton

Since Design Patterns focused on C++, we’re used to seeing singletons as classes. Since ES6, we’ve had a class syntax in JavaScript, so a basic implementation of a singleton could look something like this:

class Singleton {
  constructor() {
    if (!Singleton._instance) {
      Singleton._instance = this;
    }

    return Singleton._instance;
  }
}

Function Singleton

If we need backwards-compatibility or prefer functions over classes, we can do this with very small tweaks to the example above.

function Singleton() {
  if (!Singleton._instance) {
    Singleton._instance = this;
  }

  return Singleton._instance;
}

Accessing the Instance

Whether we’re using a class or a function, any time we call new Singleton(), we’ll be referring to the same instance. But this could easily become confusing: it’s not the expected behaviour for the new keyword.

For that reason, it’s common to provide a getInstance method on the class or function itself.

Class getInstance

class Singleton {
  constructor() {
    if (!Singleton._instance) {
      Singleton._instance = this;
    }

    return Singleton._instance;
  }

  static getInstance() {
    return this._instance;
  }
}

Once we’ve created an instance with new Singleton(), we can then call Singleton.getInstance().

Function getInstance

function Singleton() {
  if (!Singleton._instance) {
    Singleton._instance = this;
  }

  Singleton.getInstance = function () {
    return this._instance;
  };

  return Singleton._instance;
}

Once we’ve called the function Singleton(), we can then call Singleton.getInstance(). Now we know how to create singletons, should we actually be using them in our code?

The Argument For

In JavaScript, there’s one main technical advantage for singleton: lazy-loading. Unlike global variables, our singleton will be instantiated only when we first need it.

Whether this is a meaningful benefit of not will depend on your specific use-case. If a singleton contains stateful values, if it is time-consuming or memory-intensive to instantiate — and if a global variable would, therefore, block other more critical code — then you might be better off with a singleton.

The Argument Against

However, for me, it’s difficult to think of practical examples in real-life projects where this technical consideration would really offer enough advantage to justify the limitations (and potential confusion) that a singleton could create.

Imagine you want to build a Logger service that sends logs to a third-party log management tool. You need to send logs throughout the application, but you only want to authorise the third-party integration once. A singleton, a bit like the one below, might seem like a reasonable solution:

class Logger {
  constructor(config) {
    if (!Logger._instance) {
      Logger._instance = this;
      authenticateLogger(config);
    }

    return Logger._instance;
  }

  log(...logs) {
    sendLogsToLogManagementTool(...logs);
    console.log(...logs);
  }

  static getInstance() {
    return this._instance;
  }
}

export default Logger;

Now, whenever a developer tries to create a new Logger, no unnecessary code will run because we’ll still be working from just one instance.

But what if a developer doesn’t realise that they’re using a singleton? When we defined our class, it might have seemed like there would be no future need to extend the Logger function or create a second instance.

But in fact, it’s easy enough to come up with a scenario: what if we decided we wanted a specific part of the codebase to be associated with different credentials in our logging tool? Maybe different teams want to monitor different parts of the application separately.

Instead, we could simply export an instance of the Logger class:

class Logger {
  constructor(config) {
    authenticateLogger(config);
  }

  log(...logs) {
    sendLogsToLogManagementTool(...logs);
    console.log(...logs);
  }
}

const logger = new Logger(config);

export default logger;

With this solution:

  • Our class behaves as expected, so there’s no risk of confusion or misuse,
  • It’s easily extended by other developers,
  • It makes unit testing of the class and individual instances easier,
  • The class code gets a little less complex, as we no longer need a getInstance method.

The benefit of lazy-loading may be lost, but our authenticateLogger function is unlikely to be that performance-intensive anyway.

What do you think? Is there a place for singletons in JavaScript?

© 2024 Bret Cameron