Search code examples
javascriptapiobjectnativechaining

API: Making constructor functions chainable without parenthesis in JavaScript


So I am building out some chainable functions that does simple type checking. Currently, I have to call my functions like this:

Proceed().if('someString').is.a('string');

But what I really want is for my API to look like this:

proceed.if('someString').is.a('string');

Notice that in the second code sample, the opening and closing parenthesis are missing from the first function call.

As you can see from the code below, I have already figured out how to get the is and a to work, but I can't seem to find a way to remove the parenthesis from the Proceed() function.

Here is the code sample that works:

function Proceed() {
  if (!(this instanceof Proceed)) {
      return new Proceed();
    }
    this.target = "";
}

Proceed.prototype.if = function (target) {
    this.target = target;
    return this;
}

Proceed.prototype.a = function (type) {
    console.log(this.target + ' === ' +type, typeof this.target === type);
};

Object.defineProperty(Proceed.prototype, 'is', {
        get: function () {
            return this;
        }
    });


Proceed().if('someString').is.a('string'); // true
Proceed().if('someString').is.a('function'); // false


// Everything Above this line Works!

And now, my attempt to remove the parenthesis from Proceed() looks like this:

Object.defineProperty(Proceed.prototype, 'proceed', {
  set: function(){},
  get: function(){
    return Proceed(this);
  },
  configurable: true
});

proceed.if('someString').is.a('string');    // ReferenceError
proceed.if('someString').is.a('function');  // ReferenceError 

The error I get from here is this:

Uncaught ReferenceError: proceed is not defined

If I swap out Proceed.prototype with Object.prototype then I can get it to work, but that means I have extended a native object, which can be problematic.

So does anyone know of a way I can pull this off without dangerously extending the native object? What am I doing wrong here?

Here is a jsFiddle with the above code samples.

Any help is appreciated.

UPDATE #1 This code is being designed as a node module, so there won't be any access to the browser's window object.


Solution

  • You need to start with a instance-valued variable:

    var proceed = new Proceed();
    

    And to make it chainable, you should return new instances from your methods instead of mutating the "static" proceed object:

    function Proceed(target) {
        this.target = arguments.length ? target : "";
    }
    
    Proceed.prototype.if = function (target) {
        return new Proceed(target);
    }
    

    Or in general, you need to make proceed.if a factory that returns Proceed instances, regardless whether proceed already is one or just a normal object.

    (jsfiddle demo)