26

I'm using ES6 with the Webpack es6-transpiler per my article here: http://www.railsonmaui.com/blog/2014/10/02/integrating-webpack-and-the-es6-transpiler-into-an-existing-rails-project/

Does it make any sense to convert two Singleton objects to use ES6 Classes?

import { CHANGE_EVENT } from "../constants/Constants";

var EventEmitter = require('events').EventEmitter;
var merge = require('react/lib/merge');

var _flash = null;

var BaseStore = merge(EventEmitter.prototype, {

  emitChange: function() {
    this.emit(CHANGE_EVENT);
  },

  /**
   * @param {function} callback
   */
  addChangeListener: function(callback) {
    this.on(CHANGE_EVENT, callback);
  },

  /**
   * @param {function} callback
   */
  removeChangeListener: function(callback) {
    this.removeListener(CHANGE_EVENT, callback);
  },

  getFlash: function() {
    return _flash;
  },

  setFlash: function(flash) {
    _flash = flash;
  }
});

export { BaseStore };

This is file ManagerProducts.jsx that has a singleton that should extend from BaseStore.

/**
 * Client side store of the manager_product resource
 */
import { BaseStore } from "./BaseStore";
import { AppDispatcher } from '../dispatcher/AppDispatcher';
import { ActionTypes } from '../constants/Constants';
import { WebAPIUtils } from '../utils/WebAPIUtils';
import { Util } from "../utils/Util";
var merge = require('react/lib/merge');

var _managerProducts = [];

var receiveAllDataError = function(action) {
  console.log("receiveAllDataError %j", action);
  WebAPIUtils.logAjaxError(action.xhr, action.status, action.err);
};

var ManagerProductStore = merge(BaseStore, {
  getAll: function() {
    return _managerProducts;
  }
});

var receiveAllDataSuccess = function(action) {
  _managerProducts = action.data.managerProducts;
  //ManagerProductStore.setFlash({ message: "Manager Product data loaded"});
};


ManagerProductStore.dispatchToken = AppDispatcher.register(function(payload) {
  var action = payload.action;
  if (Util.blank(action.type)) { throw `Invalid action, payload ${JSON.stringify(payload)}`; }

  switch(action.type) {
    case ActionTypes.RECEIVE_ALL_DATA_SUCCESS:
      receiveAllDataSuccess(action);
      break;
    case ActionTypes.RECEIVE_ALL_DATA_ERROR:
      receiveAllDataError(action);
      break;
    default:
      return true;
  }
  ManagerProductStore.emitChange();
  return true;
});

export { ManagerProductStore };
ProgramFOX
  • 5,352
  • 10
  • 40
  • 48
justingordon
  • 11,553
  • 9
  • 64
  • 111

5 Answers5

67

No. Makes no sense.

Here's a really simple example of a singleton object in es6:

let appState = {};
export default appState;

If you really want to use a class in your singleton approach, I would recommend against using "static" as it more confusing than good for a singleton at least for JS and instead return the instance of the class as a singleton like so...

class SomeClassUsedOnlyAsASingleton {
  // implementation
}

export default new SomeClassUsedOnlyAsASingleton();

This way you can still use all the class things you like that JavaScript offers but it will reduce the confusion as IMO static isn't fully supported in JavaScript classes anyway as it is in typed languages such as c# or Java as it only supports static methods unless you just fake it and attach them directly to a class (at the time of this writing).

King Friday
  • 19,950
  • 9
  • 78
  • 78
  • 3
    Or just one line `export default let appState = {};` :-) – Bergi Apr 30 '15 at 21:57
  • 1
    @Jason: Thanks, you really opened my eyes. I also tried to define a singleton class, but having a single object is much simpler. As you can even define functions in this object, I can see no drawback at all. – waldgeist Aug 21 '15 at 09:27
  • let appState = { func: function() { // blah blah blah } }; – pyrsmk Sep 29 '15 at 06:44
  • Correct, but what are you going to do if you actually need particular class things, like extending from another class for example. You could of course use old school prototypal inheritance and return that object, but I think the OP actually prefers to use ES2015 classes for this one. – dejakob Feb 06 '16 at 16:18
  • 1
    @dejakob: es6 obj literal? I'm sure someone can point out a reason not to do this, but: `export default { __proto__: new Inherited(), newMethod() {} }` – theflowersoftime Jun 01 '16 at 17:47
  • @waldgeist , The only benefit I for using a singleton class is if you have properties you want to use getters and setters with instead of functions to get and set values which can be overwritten. – mjwrazor Feb 23 '17 at 22:10
  • 1
    And where do you put the class? Where's the constructor? Why not show an example? – shinzou Apr 25 '17 at 10:39
  • Of course, I'm the troll, and you're being very useful and helpful, or maybe you just don't know and that's the best reply you could come up with. And you're also expecting me to praise bad answers, not everyone is a hypocrite. – shinzou Apr 25 '17 at 13:26
  • what if you want to pass arguments to the constructor? – Sagiv b.g Feb 15 '18 at 11:33
  • I use a config myself in that case as its a singleton and used only once, where the config in imported alongside the class declaration. – King Friday Feb 15 '18 at 18:02
  • I agree that this answer is insufficient. Can the author elaborate on how the described behaviour is similar to a singleton? – Apollo Nov 13 '19 at 10:06
  • @Apollo so I do this on simple things FYI not for everything. In node or JS in general, if you export an object, it is a singleton by the definition of being a global object. You can access it modify it, etc and its globally visible everywhere using it. – King Friday Nov 13 '19 at 15:35
40

I'd argue that singletons (classes that manage their own singleton lifetime) are unnecessary in any language. That is not to say that singleton lifetime is not useful, just that I prefer that something other than the class manage the lifetime of an object, like a DI container.

That being said, the singleton pattern CAN be applied to JavaScript classes, borrowing the "SingletonEnforcer" pattern that was used in ActionScript. I can see wanting to do something like this when porting an existing code base that uses singletons into ES6.

In this case, the idea is that you make a private (via an un exposed Symbol) static singleton instance, with a public static instance getter. You then restrict the constructor to something that has access to a special singletonEnforcer symbol that is not exposed outside of the module. That way, the constructor fails if anyone other than the singleton tries to "new" it up. It would look something like this:

const singleton = Symbol();
const singletonEnforcer = Symbol()

class SingletonTest {

  constructor(enforcer) {
    if(enforcer != singletonEnforcer) throw "Cannot construct singleton";
  }

  static get instance() {
    if(!this[singleton]) {
      this[singleton] = new SingletonTest(singletonEnforcer);
    }
    return this[singleton];
  }
}

export default SingletonTest

Then you can use it like any other singleton:

import SingletonTest from 'singleton-test';
const instance = SingletonTest.instance;
Brian Genisio
  • 46,643
  • 16
  • 121
  • 163
  • 12
    http://amanvirk.me/singleton-classes-in-es6/ is better approach , as it manage the state inside the constructor itself – Aman Virk May 20 '15 at 09:45
  • 1
    @AmanVirk Does that method work if you are extending the class? – jedd.ahyoung Sep 03 '15 at 17:54
  • 1
    IMO it is a bit weird using this in the instance getter. It can not refer to the class itself since it is a static method. I would rather put ``` const instance = null; ``` outside the class and then use instance in the code. A processor will put all of this in a closure anyways, so it will be a private variable in the end. Besides that, it seems a good solution! – dejakob Feb 06 '16 at 16:12
  • @dejakob Referring to `this` in a static method will reference the CLASS (prototype, to be exact), not the object. By using a symbol which is defined out of the class, we are essentially creating a private class member (which is different from a private instance member). The only reason to do this is to maintain class semantics. I strongly prefer to not use singletons, however. I much prefer the container be responsible for the lifetime. I don't like it when classes are responsible for their lifetime. It always feels wrong. – Brian Genisio Feb 07 '16 at 21:45
  • Why not `const singletonTest = Object.create({ }); export default singletonTest;`? – Darlesson Feb 10 '17 at 19:46
  • 1
    @Darlesson Yeah, I think that is what I'd do in practical cases. This is an example of the "classic" singleton pattern where the class itself is exported and the "instance" is a class-level property. I don't ever write code this way. – Brian Genisio Feb 11 '17 at 22:20
  • @AmanVirk this doesn't work if you try to singleton an extending class. – shinzou Apr 25 '17 at 10:38
12

I had to do the same so here is a simple and direct way of doing a singleton, curtsy to singleton-classes-in-es6

(original link http://amanvirk.me/singleton-classes-in-es6/)

let instance = null;

class Cache{  
    constructor() {
        if(!instance){
              instance = this;
        }

        // to test whether we have singleton or not
        this.time = new Date()

        return instance;
      }
}


let cache = new Cache()
console.log(cache.time);

setTimeout(function(){
  let cache = new Cache();
  console.log(cache.time);
},4000);

Both console.log calls should print the same cache.time (Singleton)

campisano
  • 197
  • 1
  • 9
AdrianD
  • 269
  • 2
  • 18
  • 2
    @shinzou What makes you think it doesn't work with an extending class? All subclasses will return the same singleton instance, that's how singletons work. – Bergi Dec 23 '17 at 19:44
5

In order to create Singleton pattern use a single instance with ES6 classes;

'use strict';

import EventEmitter from 'events';

class Single extends EventEmitter {
    constructor() {
        this.state = {};
    }

    getState() {
        return this.state;
    }

}

export default let single = new Single();

Update: According to @Bergi explanation, below one is not a valid argument.

This works because of (refer to Steven)

> If I understand CommonJS + the browser implementations correctly, the > output of a module is cached, so export default new MyClass() will > result in something that behaves as a singleton (only a single > instance of this class will ever exist per process/client depending on > env it's running in).

You can find an example here ES6 Singleton.

Note: This pattern is using in Flux Dispacher

Flux: www.npmjs.com/package/flux

Dispacher Example: github.com/facebook/flux/blob/master/examples/flux-todomvc/js/dispatcher/AppDispatcher.js#L16

Milan Karunarathne
  • 80
  • 1
  • 3
  • 10
  • Don't do this! Just should not use `class` syntax when you don't want a class! Even if this pattern is used in Flux, it's not necessarily a good pattern. Btw, that quote is quite wrong, it's trivial to create a second instance using `new singleton.constructor`. – Bergi Nov 12 '15 at 22:56
  • @Bergi lets assume that Dispatcher is a class implementation and I want to extend its functionalities. So, I create another class called AppDispacher by using JS ES6 `extends` and need a single instance of that class. Then do I have to use [this approach](http://stackoverflow.com/a/26227662/4640228)? But **Jason** said "Makes No Sense". So, what's your approach?. I didn't understand one thing. You make this answer wrong. But it's working and used in a company like Facebook. :) – Milan Karunarathne Nov 13 '15 at 12:11
  • Well that might make sense if you use multiple dispatchers in your app, but I wouldn't call it a "singleton" then any more even if you have modules that export just a single instance. – Bergi Nov 13 '15 at 12:20
  • @Bergi as I understood, this pattern can't called as the "Singleton". But it allows to have a single instance which can accessible in multiple places (in an application), and there isn't any issue in using that. – Milan Karunarathne Nov 13 '15 at 12:32
  • 1
    While technically not necessary, I like this pattern since it lets you use 'extends' – sam schonstal Mar 23 '16 at 16:11
  • What if your class needs to get parameters first in its ctor? – shinzou Apr 25 '17 at 10:43
0

Singleton class

class SingletonClass {
    constructor( name = "", age = 0 ) {
        if ( !this.constructor.instance ) {

            this.constructor.instance = this;
            this.name = name;
            this.age = age;

        }

        return this.constructor.instance;
    }

    getName() {
        return this.name;
    }

    getAge() {
        return this.age;
    }
}
    
const instanceOne = new SingletonClass( "One", 25 );
const instanceTwo = new SingletonClass( "Two", 44 );

console.log( `Name of instanceOne is "${instanceOne.getName()}"` );
console.log( `Name of instanceTwo is "${instanceTwo.getName()}"` );
M. Hamza Rajput
  • 3,508
  • 1
  • 20
  • 20