This page looks best with JavaScript enabled

Gotchas of Singleton Classes in Javascript

 ·   ·  ☕ 3 min read

Be careful about using singleton patterns in Javascript classes. It may have unexpected side-effects and can be the most dreaded evil overlord you encounter on that day.

Let us take this example of an Account class that is used by all logged-in users. Users set a role and are shown accounts based on the role.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
"use strict";
const role = "mortal";

class Account {
  getAccounts() {
    console.log("this.role: ", this.role);
    if (this.role == "mortal") return "mortal accounts";
    else if (this.role == "manager")
      return "hail manager..! here's all accounts";
  }

  setRole(roleVal) {
    this.role = roleVal;
  }
}

module.exports = new Account();

Let’s consider the below test class that calls the Account class.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
const Account = require("./Account");

const mgr = Account;
const rep = Account;

mgr.setRole("manager");
// set role

console.log("mgr.getAccounts(): ", mgr.getAccounts());
// hail manager..! here's all accounts

console.log("rep.getAccounts(): ", rep.getAccounts());
// hail manager..! here's all accounts
// wrong role

Once the role is set, it is applicable for all instances of the class.

While the truly global behaviour is useful in a few cases (e.g. get database connection string), it will prove detrimental to business interests. After all, not every mortal can be hailed as manager.

What are the attributes of a singleton pattern?

How can I know whether the given class that I inherited from a big bad company is Singleton?

Apart from the code structure (which is evident), singletons also exhibit these strange behaviours -

  • accessible globally via a static field

  • created once, and being used over and over across classes, objects and methods

  • cannot be instantiated (meaning no let account1 = new Account())

  • does not allow classes to extend from the singleton class

  • cannot be used in interfaces

  • variable updates (mutations) can be dangerous and cannot be controlled easily when multiple entities want to update same value

What’s the alternative?

Normally you would want to write classes like this -

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
"use strict";

class Account {
  role = "mortal";
  constructor() {}

  getAccounts() {
    console.log("this.role: ", this.role);
    if (this.role == "mortal") return "mortal accounts";
    else if (this.role == "manager")
      return "hail manager..! here's all accounts";
  }

  setRole(roleVal) {
    this.role = roleVal;
  }
}

module.exports = Account;

You can then invoke Account class like so -

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// test.js
const Account = require("./Account");

const mgr = new Account();
const rep = new Account();

mgr.setRole("manager");
console.log("mgr.getAccounts(): ", mgr.getAccounts());

console.log("rep.getAccounts(): ", rep.getAccounts());

The variable values are local to the object and never mixed.

What if you need global variables?

Well, you can always use a single “instance” of the class, initiate values, and pass that object around using constructors and such. That is what smarter ones call “dependency injection”.

I can’t believe it. Are you saying singleton patterns do not have any use?

Yes and no. I do not use them since I don’t understand where they have to be used. And.. also because I am not single.

Seriously, though. No don’t use them- there are better options.

Stay in touch!
Share on

Prashanth Krishnamurthy
WRITTEN BY
Prashanth Krishnamurthy
Technologist | Creator of Things