8

I am working with a RESTful API, and my Javascript code is making REST queries via jQuery's $.ajax() call.

I have implemented a javascript Rest class, which I will show below (greatly simplified):

var Rest = function (baseUrlPath, errorMessageHandler) {
        ...
    };

// Declare HTTP response codes as constants
Rest.prototype.STATUS_OK = 200;
Rest.prototype.STATUS_BAD_REQUEST = 400;

... // other rest methods 

Rest.prototype.post = function (params) {
        $.ajax({
            type: 'POST',
            url: params.url,
            data: params.data,
            dataType: 'json',
            contentType: 'application/json; charset=utf-8',
            beforeSend: this._authorize,
            success: params.success,
            error: params.error || this._getAjaxErrorHandler(params.errorMessage)
        });
        };

... // more rest methods

Rest.prototype.executeScenario = function (scenarioRef) {
        var self = this;

        this.post({
            url: 'myurlgoeshere',
            data: 'mydatagoeshere',
            success: function (data, textStatus, xhr) {
                if (xhr.status == 200) {
                    console.log("everything went ok");
                }
            },
            error: function (xhr, textStatus, errorMsg) {
                // TODO: constants
                if (404 == xhr.status) {
                    self.errorMessageHandler("The scenario does not exist or is not currently queued");
                } else if (403 == xhr.status) {
                    self.errorMessageHandler("You are not allowed to execute scenario: " + scenarioRef.displayName);
                } else if(423 == xhr.status) {
                    self.errorMessageHandler("Scenario: " + scenarioRef.displayName +  " is already in the queue");
                }
            }
        });
    };

The code works as intended, however I have decided to add some constants to help beautify the code and improve readability. I have for example several places in my code where I am checking for xhr.status == 200 or xhr.status == 400 and so on.

I can declare class variables as Rest.prototype.STATUS_OK = 200;

But variable is editable, and I cannot think of how to make them constant. In my code for example I can do a this.STATUS_OK = 123; and this will modify the variable. I have played around with the const keyword, with no luck.

i have seen this: Where to declare class constants?, but it was not much help.

Can someone point me in the right direction as to how to make these fields a constant literal instead of a variable?

Community
  • 1
  • 1
Husman
  • 6,819
  • 9
  • 29
  • 47
  • Please notice that "fields" are *properties*, not *variables*. For variables you simply could use the [`const` keyword](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const) where supported. But you would local-scope it anyway :-) – Bergi Jul 25 '13 at 13:54

4 Answers4

13

Using ECMAScript 5's Object.defineProperty you can make a value un-settable:

Object.defineProperty(Rest, "STATUS_OK", {
  enumerable: false,   // optional; if you care about your enumerated keys
  configurable: false,
  writable: false,
  value: 200
});

Or, since those are the default values, simply do:

Object.defineProperty(Rest, "STATUS_OK", { value: 200 });

This makes Rest.STATUS_OK yield 200 when accessed, but it will not respond to attempts to redefine it or delete it. Furthermore, configurable: false will prevent any attempt to redefine the property with a subsequent defineProperty call.

However, this doesn't work in older browsers that don't support ES5's defineProperty (notably IE8 and below).

apsillers
  • 112,806
  • 17
  • 235
  • 239
  • That is a good workaround, but it seems like a kludge to have to write several lines of code (i know its one statement, but still several lines for readability) to set a single constant. And definately a lot of work when you have lots of constants to declare. Perhaps there will be better support for this in the future as IE8 and below are phased out. – Husman Jul 25 '13 at 13:51
  • 5
    @Husman: Actually you can omit all lines but the `value`, since `false` is the default for those. – Bergi Jul 25 '13 at 13:51
  • @Bergi Good call; I edited my answer according to your suggestion. – apsillers Jul 25 '13 at 13:53
  • @Bergi still seems like a hack, considering you have to call the global Object and a defineProperty, then pass in your Object, variable and the properties you want to set. Most languages have 'var x' and 'const x' or something similar i.e. final (java) or #define (C/C++) – Husman Jul 25 '13 at 13:55
  • I am going to select this as the accepted answer, as there were some good suggestions by both the author and @Bergi, however this is unusable for me at this point in time as the code is for a corporate client and IE support is a must. Thanks for all the help guys. – Husman Jul 25 '13 at 13:58
  • Can't you define `window.const()` as a helper that does the dirty work? It's not even all that dirty, really... – Steven Lu Jul 03 '15 at 00:35
2

This will not be possible in Javascript. Best thing you could probably do, is create some closure like stuff:

var StatusCode = (function() {
    var STATUS_OK = 200,
        STATUS_BAD_REQUEST = 400;

    return {
        getOk: function() {
            return STATUS_OK;
        },
        getBadRequest: function() {
            return STATUS_BAD_REQUEST;
        }
    }

});

And use it like StatusCode.getOk() === 200. This would help you to not be able to change those 'constants', but will again be bad for your readability (this is probably opinion based). I would just keep those constants all uppercase to mark them as constant, although they could be changed.

Dennis
  • 14,210
  • 2
  • 34
  • 54
  • 2
    +1; Furthermore, there's no reason why someone couldn't simply overwrite `getOk` with `function() { return "something else"; }`. – apsillers Jul 25 '13 at 13:30
  • 2
    @apsillers good point. As a bottom line, you cannot safely protect anything in javascript. Typical example - even `undefined` can be overridden. – Dennis Jul 25 '13 at 13:32
1

You could define the statuses as getters, but AFAIK this won't work in IE8 and older.

var Rest = function (baseUrlPath, errorMessageHandler) {
        this.STATUS_OK = 123; // trying to override.
    };

// Declare HTTP response codes as constants
Rest.prototype = {
    get STATUS_OK(){ return 200; },
    get STATUS_BAD_REQUEST(){ return 400; }
}

var client = new Rest();
console.log( client.STATUS_OK ); // 200!
client.STATUS_OK = 123;
console.log( client.STATUS_OK ); // still 200!

More on getters and setters: http://ejohn.org/blog/javascript-getters-and-setters/

pawel
  • 35,827
  • 7
  • 56
  • 53
  • `Object.defineProperty(Rest.prototype, "STATUS_OK", {get:function(){return 123;}});` and you're out :-) – Bergi Jul 25 '13 at 13:45
0

Javascript doesn't have a good support to create immutable constants. Even the const keyword isn't recommended because doesn't work in some browsers.

I think the best way todo it is using Object.freeze:

Rest.Status = {};
Rest.Status.Ok = "Ok";
Object.freeze(Rest.Status);

Object.freeze will silent ignore changes in the Status object. By example:

Rest.Status.Ok = "foo";
Rest.Status.Ok; //=> "Ok"

But just work in ECMAScript 5 or above.

Above I have placed the status in a Status object, I think that it is more interesting than prototype, because prototype is more close to instance methods, properties etc. And the Status object seen like a enumeration.

Marcio Junior
  • 19,078
  • 4
  • 44
  • 47
  • Note that this still allows reassignment of the `Rest.Status` property to a completely different object. – apsillers Jul 25 '13 at 14:07
  • Yes, I don't had realized it. One alternative would be create a RestStatus object, but that way will exist two global variables :( – Marcio Junior Jul 25 '13 at 14:19