ES5 JavaScript - Using Getters to create a self-aware object

25 Aug 2017

Let’s imagine you’re on a large project. You have the following limitation - ES5 Javascript.

Now, what happens in large codebases with 20k+ lines? They will end up with files called config.js and contact_info.js and environments.js, etc. Reference files that are frequently used.

In my case, our project has some code that (for example), shouldn’t run in production, but it should run in a development environment.

We had a file that looked like this:

const ENVs = {
    // Arrays of strings. We use the ENV=[environment] condition when running scripts.
    // E.G. our CI will run ENV=test node script.js
    is_dev: ['development', 'dev_server_2'],
    is_prod: ['production'],
    is_test: ['test', 'aws_test'],
    is_aws: ['aws_test'],
};
module.exports = ENVs;

So here’s how we typically would use this. Let’s say we don’t want a certain part of code to run if we’re in the production environment (a pretty common need).

Using Node.js to get the ENV environmental variable, we would check like this:

const environment = require('./environment.js')

const env = process.env.ENV; // Get the ENV environmental variable using Node

if (environment.is_prod.indexOf(env) > -1) {
    return; // end the script
}

// one liner. this is what ends up happening in real life
if (environment.is_prod.indexOf(process.env.ENV) > -1) return;

If you’re saying dude, just use .includes() - remember, ES5!


Okay, so let’s say we have an urge to make this more readable across the codebase. What’s one way to solve this?

Why, sure, let’s introduce some more overhead to this process! :)

Let’s modify that object we worked with earlier.

const ENVs = {
    dev: ['development', 'dev_server_2'],
    prod: ['production'],
    test: ['test', 'aws_test'],
    aws: ['aws_test'],
   
    get is() {
        // Utility lookup functions added here
    }  
};

We’ve added a getter to the object called is. is is an object that will contain pre-computed responses about the environment we’re working in.

In the end, we want to be able to make a call like this: environment.is.dev. The logic for a self-aware object is as follows:

get is() {
    return Object.keys(this).filter(k => k !== 'is').reduce((accum, key) => {
        accum[key] = this[key].indexOf(process.env.ENV) > -1;
        return accum;
    }, {})
}

The whole thing ends up looking like this.

const ENVs = {
    dev: ['development', 'dev_server_2'],
    prod: ['production'],
    test: ['test', 'aws_test'],
    aws: ['aws_test'],
    get is() {
        return Object.keys(this).filter(k => k !== 'is').reduce((accum, key) => {
            accum[key] = this[key].indexOf(process.env.ENV) > -1;
            return accum;
        }, {})
    }
};

module.exports = ENVs;

Our developers can now do something easy when they’re trying to determine where the hell their code is running.

const env = require('./environment.js')

if (env.is.prod) {
    return; // don't run in prod!!!
} 
if (env.is.dev || env.is.test) {
    // do dev or test stuff
}

This sort of self-aware object makes working with constant data much simpler. If you know a cleaner way to do this, email me :) I’ll give you a prize. The prize is me sending you an email back btw.