Search code examples
javascriptnode.jssingletongetter-setter

Why does my Singleton Module seem to have two instances?


The Problem

I am trying to create a Singleton which stores information that can only be accessed with Setters and Getters. I like to keep my code as short and clean as possible so I am always looking for the shortest way of writing things. While this works in most places, in others I tend to create some not so explainable (to me) errors.

So,.. when importing my module for testing I use:

const { ports, instance: activeConfig } = require('./activeConfig')

ports should be my Getter. I wanted to avoid having to always write out activeConfig.ports when possible. I thought for the most part this was working in my test until I tried to reset my values.

A few things I have observed:

  1. When I run activeConfig.ports my getter is activated. I can see this before log result 1, 2 and 3.
  2. When I run just ports my getter is NOT activated. I can see from log result 1a, 2a and 3a that nothing is run inside the Singleton. But I still get results.
  3. When I set a new values I can see inside the Singleton my value for _myPorts does change. The result for both methods also is as I expected.

However, after I reset my values this behavior no longer stays consistent. While activeConfig.ports provides the expected result, ports does not. I am not sure where log 3a gets this result from and why it differs from log result 3.

I have apparently built in some sort of instability but my JavaScript skills are not strong enough to pinpoint the problem. I have tried googling all variations of "JavaScript Singleton Getter Export" but cant seem to find an example that covers my specific use case.

If anyone could help me fix my code it would be greatly appreciated. Also, little things like the correct terminology or links to advanced examples would really be appreciated. :)

My main focus is to be able to import getters in a single require command!
I have simplified this example, but I would like to be able to do this with x number of getters.

I am not looking for work-arounds like:

const activeConfig = require('./activeConfig')
const ports = activeConfig.ports

or

const ports = require('./activeConfig').ports

This would require one line per getter and I am trying to avoid this exact situations. (Especially with 10+ getters)

I would really like to know if there is a correct way of doing this. :)

The Code

'use strict'
let activeConfig = (function () {
  const _defPorts = {
    http: 80,
    https: 443,
    secure: false
  }

  let _myPorts = {..._defPorts}

  let _setPorts = function (value) {
    console.log('SP->', _myPorts)
    if (value) {
      Object.keys(value).forEach((key) => {
        if (typeof _myPorts[key] !== 'undefined') {
          _myPorts[key] = value[key]
        }
      })
    }
    console.log('SP=>', _myPorts)
  }

  let _resetConfig = function () {
      console.log('RC->', _myPorts)
      _myPorts = {..._defPorts}
      console.log('RC=>', _myPorts)
    }


  return {
    setPorts: _setPorts,
    resetConfig: _resetConfig,
    get instance() {
      return activeConfig 
    },
    get ports() {
      console.log('GP->', _myPorts)
      return _myPorts
    },
    set ports(value) {
      return _setPorts(value)
    }
  }
})()
module.exports = activeConfig

The Test Code

'use strict'
const {
    ports,
    instance: activeConfig
} = require('./activeConfig')

console.log('1 ->', activeConfig.ports)
console.log('1a->', ports)
activeConfig
    .setPorts({
        secure: true,
        http: 8080
    })

console.log('2 ->', activeConfig.ports)
console.log('2a->', ports)
console.log('RESET')
activeConfig.resetConfig()
console.log('3 ->', activeConfig.ports)
console.log('3a->', ports)

The Log

GP-> { http: 80, https: 443, secure: false }
GP-> { http: 80, https: 443, secure: false }
1 -> { http: 80, https: 443, secure: false }
1a-> { http: 80, https: 443, secure: false }
SP-> { http: 80, https: 443, secure: false }
SP=> { http: 8080, https: 443, secure: true }
GP-> { http: 8080, https: 443, secure: true }
2 -> { http: 8080, https: 443, secure: true }
2a-> { http: 8080, https: 443, secure: true }
RESET
RC-> { http: 8080, https: 443, secure: true }
RC=> { http: 80, https: 443, secure: false }
GP-> { http: 80, https: 443, secure: false }
3 -> { http: 80, https: 443, secure: false }
3a-> { http: 8080, https: 443, secure: true }

Solution

  • Object destructuring is just syntactic sugar around property access:

     const { port } = obj;
     // equals
     const port = obj.port;
    

    so at the time you destructure, you do access the getter. Changing port changes the local variable. It does not get reflected to the object, and thus does not trigger the setter.

    There is no real way to use getters / setters in that way (okay, lets forget about with).

    Instead, you could manually trigger unreal setters:

      const obj = {
        port: { set(_v) { return this._v = _v; }, get() { return this._v; } }
      };
    
     const { port } = obj;
     port.set(10);