2

I've tried to search for instance caching and singletons on Google and StackOverflow without success, seeing only posts about module.exports, if you know a post that answers this question, feel free to reference it. Thank you!

I have an application that needs to work on a set of objects that rarely change, and hence need to be cached for performance optimisation.

Here is a toy example where a single property is set directly.

When I call the application, I export an object that will contain the set of cached objects in assets_cached.js:

const Assets = {};
module.exports.Assets = Assets;

In another module of the application I have an ES6 class:

const _ = require('lodash')
const { Assets } = require('./assets_cached')

class Asset {
    constructor(id, some_property) {
        if (id in Assets) {
            // Update instance data with cached properties
            _.assign(this, Assets_cached[id]);
        } else {
            // If it's not cached, create a new object
            this.id = id;
            this.some_property = some_property;
            // Cache this object
            Assets_cached[id] = this;
        }
    }

    getProperty() {
        return this.some_property;
    }

    setProperty(value) {
        this.some_property = value;
        // Is there a way of avoiding having to do this double assignment?
        Assets_cached[id].some_property = value;
    }
}
module.exports = Asset;

How may I avoid having to set the some_property twice (in the current instance and the cache, while ensuring that other instances are updated in parallel)?

Ideally I'd like to do something like:

if (id in Assets) {
    this = Assets.cached[id]
}

inside the constructor, but this is not possible.

What's the most elegant and correct way of making this work?

alexvicegrab
  • 481
  • 3
  • 17
  • Why do you want to have multiple `Asset` instances for the same asset? – Bergi Nov 08 '17 at 18:52
  • 1
    One does not want to handle the caching within the constructor. The constructor is the place for the least necessary code in order to create an instance. But one might think about moving all the additional logic into a factory function like `Assets.create`. This also plays nicely with Bergi's first quick answer. – Peter Seliger Nov 08 '17 at 19:04

2 Answers2

2

Ideally I'd like to do something like this = Assets.cached[id] inside the constructor

The magic keyword here is return. You can just return an arbitrary object from the constructor and it will be used instead of this.

constructor(id, some_property) {
    if (id in Assets) {
        // use cached instance instead of creating a new one
        return Assets_cached[id];
    } else {
        this.id = id;
        this.some_property = some_property;
        // Cache this object
        Assets_cached[id] = this;
    }
}
Bergi
  • 513,640
  • 108
  • 821
  • 1,164
  • Thank you Bergi, that's very helpful, I'm relatively new to Node and JS and had not expected it to be quite as simple as that. – alexvicegrab Nov 09 '17 at 11:45
1

Here is the approach to the comment that was made some half an hour ago ...

const { Assets_cached } = require('./assets_cached');
// const { AssetStore } = require('./assetstore');

class Asset {
    constructor(id, some_property) { // clean/lean constructor.
        this.id = id;
        this.some_property = some_property;
    }
    getProperty() {
        return this.some_property;
    }
    setProperty(value) {
        this.some_property = value;
    }
}

function isAsset(type) {
    // poor man's approach ... change to something more feasible.
    return (type instanceof Asset);
}

function createAsset(id, some_property) { // factory that also handles caching.
    var
        asset = Assets_cached[id];
    //  asset = AssetStore.get(id);

    if (!(asset && isAsset(asset))) {

        asset = Assets_cached[id] = (new Asset(id, some_property));
    //  AssetStore.put(id, (asset = new Asset(id, some_property)));
    }
    return asset;
}

module.exports = {
    create  : createAsset,
    isAsset : isAsset
};

Note

One also should consider providing a minimal API to Assets_cached, something like put/set, get and delete instead of Assets_cached being an entirely exposed, plain key-value store.

Peter Seliger
  • 4,001
  • 1
  • 20
  • 27
  • Thanks Peter. I would have used a factory initially, but some of the code already is in production so need to be able to return an existing object from the constructor. – alexvicegrab Nov 09 '17 at 11:46