Radex
Radex

Reputation: 8587

How to chain js functions?

Let's image I have a number and I want to perform some arithmetic operations example.

x.add(3).subtract(2).print() // result 1

or alternatively

0.add(3).subtract(2).print() // result 1 (I have no code for this)

Currently I am trying the following code. I would like to know if there is a better more succinct way to achieve the same result,

Thanks

var test = function () {

    var i = 0;

    var add = function (j) {
        i += j;
        return this;
      };

    var subtract = function (j) {
        i -= j;
        return this;
      };

    var print = function () {
        console.log(i);
      };

    return {
        add: add,
        subtract: subtract,
        print: print
    };
  };

var x = test();

x.add(3).subtract(2).print();

Upvotes: 3

Views: 5361

Answers (4)

jpolete
jpolete

Reputation: 898

I know this is an older post, but I was recently looking for a way to chain functions to make them more readable and came up with a simple recursive function that lets you chain as many "then" operations as you want then call "eval" to get the result.

You can do things like this.

// Result = 18 (Start with 3 then square it, then double it)
const result = then(3).then(square).then(double).eval();

function double(x) {
  return x + x;
}

function square(x) {
  return x * x;
}

You could pass in predefined functions like above or arrow functions like below. Your example could be done like this.

const result = then(0).then(x => x + 3).then(x => x - 2).eval();

It's not exactly the syntax you're looking for, but it allows indefinite chaining of operations.

Here's the code for "then".

function then(arg) {
    return {
        "eval": () => arg,
        "then": (op) => then(op(arg))
    };
}

Upvotes: 0

T.J. Crowder
T.J. Crowder

Reputation: 1075587

There are almost two separate answers to this question:

  • A hyper-general answer.

  • A general answer applicable to objects in, er, general.

  • A specific answer applicable to numbers.

Hyper-General Answer

You can chain functions any time an earlier function returns an object (or something that can be implicitly coerced to an object) that has another function as a property (loosely, a "method"). So for instance, you can chain .toString().toUpperCase() on a Date:

var dt = new Date();
console.log(dt.toString().toUpperCase();

...because Date.prototype.toString returns a string, and strings have a toUpperCase method.

General Answer Applicable to Objects in, er, General

One common and useful pattern is to have an object whose methods return a reference either to that same object or to another object of the same "type." (Sometimes people say "shape" rather than "type," since JavaScript is largely untyped.) jQuery uses this to great effect, for instance. $() returns a jQuery object; depending on what method you call on it, you'll either get back the same jQuery object (for instance, each) or a new jQuery object representing the result of the operation (for instance, find). This pattern is quite useful, both with mutable objects and especially with immutable ones.

To do that in your own object: To return the same object just return this. To return a new object of the same "shape," you'd use whatever construction mechanism you use for your object to create the new object to return.

So for instance, if we add a function at the beginning of your sequence that creates a "streamed" number, we'd either make it mutable and return this each time:

function nstream(val) {
  return {
    // Our add operation returns this same instance
    add: function(n) {
      val += n;
      return this;
    },
    // Same with multiply
    multiply: function(n) {
      val *= n;
      return this;
    },
    // To access the underlying value, we need an accessor
    result: function() {
      return val;
    },
    // This provides compatibility with built-in operations
    // such as + and *
    valueOf: function() {
      return val;
    }
  };
};
// Using only nstream ops:
console.log(nstream(1).add(3).multiply(4).result());
// Implicitly using valueOf at the end:
console.log(nstream(1).add(3) * 4);

Or we might make nstreams immutable, where each operation returns a new nstream:

function nstream(val) {
  return {
    add: function(n) {
      return nstream(val + n);
    },
    // Same with multiply
    multiply: function(n) {
      return nstream(val * n);
    },
    // To access the underlying value, we need an accessor
    result: function() {
      return val;
    },
    // This provides compatibility with built-in operations
    // such as + and *
    valueOf: function() {
      return val;
    }
  };
};
// Using only nstream ops:
console.log(nstream(1).add(3).multiply(4).result());
// Implicitly using valueOf at the end:
console.log(nstream(1).add(3) * 4);

Paradoxically, immutable objects like that can either increase or decrease memory pressure in your code, depending on whether they help you avoid defensive copies. For instance, if an nstream were a member of another object and you wanted to know it couldn't change, if it were mutable you'd have to make a defensive copy when handing it out to other code; if it's immutable, you don't have to, but you have to create copies when "modifying" it. But costs and benefits of immutable objects are a bit afield of the question. :-)

Specific Answer Applicable to Numbers

As you're trying to call a method on a number, you'd have to add that method to Number.prototype. Then you'd have your methods (add, multiply, etc.) return the result of the operation, which being a number will have your other methods. Here's a quick example:

Object.defineProperties(Number.prototype, {
  add: {
    value: function(n) {
      return this + n;
    }
  },
  multiply: {
    value: function(n) {
      return this * n;
    }
  }
});
console.log(1..add(3).multiply(4));

A couple of notes on that:

  • As always when extending built-in prototypes, it's important to use Object.defineProperty or Object.defineProperties without enumerable: true so the new properties aren't enumerable. (Though with numbers it's not a really big deal. Much more so with arrays. And we leave plain objects [e.g., Object.prototype] alone entirely.)
  • The .. in 1..add(3) might look odd, but it's one of the two ways you call methods on numeric literals. The first . is the decimal point. The second . is the property accessor operator. Another way is (1).add(3) because there's no confusion where the numeric literal ends. Obviously, this doesn't come up with variables: var n = 1; console.log(n.add(3).multiply(4)); works just fine.
  • There's a fair bit of under-the-covers conversion between Number objects and number primitives going on there: When you access a property (including a function property) on a primitive (number, string, boolean), the JavaScript engine coerces the primitive to its equivalent object type (Number, String, Boolean) and then looks for the property on that object. Since it's a freshly-created object, the only properties it'll have come from its prototype. So 1..add(3) creates a new Number object, then calls add on that object. Within add when we do return this + n, the + coerces the object back to its primitive value. Of course, all of this is optimized where possible and where it matters by the JavaScript engine.

Upvotes: 10

Valeria Viana Gusmao
Valeria Viana Gusmao

Reputation: 465

You can chain methods as long as the return of previous method has the following one. So, in this case you need to create a "class" that thas has all the needed method and then return an "instance" of that class in all of them.

var myNum = function(n) {

  var num = n;

  this.add = function(a) {

   var b = n+a;
   return new myNum(b) ;

  } 

} 

Upvotes: 1

Nina Scholz
Nina Scholz

Reputation: 386858

You could add some prototypes to the Number object and call the value with a dot for floating point and a dot for the method.

Changing proptotypes of standard objects are not advisable, because they could lead to misunderstanding of code.

Number.prototype.add = function (v) { return this + v; };
Number.prototype.multiply = function (v) { return this * v; };

console.log(1..add(2).multiply(4)); // 12

Upvotes: 0

Related Questions