Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to detect the end of a method chain in JavaScript?

Firstly and most importantly I'm trying to detect the end call of a method chain. I would also like to devise a way to detect how many methods "in" or "down" an object chain I am within my method calls in a method chain.

For instance, in the plugin I'm writing:

var result = $("#someDiv").myPlugin.foo().bar()._foo()._bar();

Say the method is currently executing in .bar() I would like to know that I'm 2 methods down the chain.

The reason I need to abstract this information in some manner is so when I reach the last method in the chain I can return a result instead of the plugin object thus breaking the chain at that point for the sake of gaining access to our data in the 'result' variable.

like image 585
Xaxis Avatar asked Jan 02 '13 14:01

Xaxis


People also ask

What is method chaining in JavaScript?

Method Chaining is a programming strategy that simplifies and embellishes your code. It is a mechanism of calling a method on another method of the same object. this keyword in JavaScript refers to the current object in which it is called. Thus, when a method returns this, it simply returns an instance of the object in which it is returned.

How do you end a string with another string in JavaScript?

For this solution, you’ll use the String.prototype.endsWith () method: The endsWith () method determines whether a string ends with the characters of another string, returning true or false as appropriate. This method is case-sensitive.

Why do we need method chaining in Python?

Now we can change the part where we create the car object with the more readable and less repeating code − Method chaining is also called fluent interface as it allows to operate on the object through methods without breaking the flow again and again by repeating the object.

How to check whether a string ends with specific sequence of characters?

This involves checking whether a string ends with specific sequence of characters. Check if a string (first argument, str) ends with the given target string (second argument, target ). This challenge can be solved with the .endsWith () method, which was introduced in ES2015.


2 Answers

Here's an example pulled from your project:

var strLenA = parseInt( P.strlen('some string').data );
var strLenB = parseInt( P.strlen('another string').data );
var totalStrLen = strLenA + strLenB;
console.log(strLenA, strLenB, totalStrLen);

From this I can see why our answers aren't really adequate - and why you want to get rid of .data. Happily, your .data always returns a string, anyway. So, you can use the mystical .toString override to have your methods still return a copy of the parent method - but also allow for them to be treated like strings.

Here's an example: [with a fiddle]

var stringMagic = function() {
    var chain = "",
        self = this;
    self.toString = function () { return chain; }; // Where the real magic happens.
    self.add = function(str) {
        chain += str + " ";
        return self;
    };
};


var magi = new stringMagic();
alert(magi.add("hello").add("world")); // Alerts, "hello world"
magi.add("and").add("thanks").add("for").add("all").add("the").add("fish");
alert(magi); // Alerts, "hello world and thanks for all the fish"

In your case, probably all you'd have to do is change .data in P to .toString and wrap it in a function.

In the future when you add support for other data types such as numbers and booleans, you can use valueOf in the same way you use toString. In fact, you should also continue to include toString when the return value is not a string for when they're treating that number as a string - like in console.log or $.fn.text. Here's the example above, but with numbers: http://jsfiddle.net/emqVe/1/

like image 188
Grinn Avatar answered Oct 24 '22 01:10

Grinn


For the sake of completeness. Yet another alternative is to pass a an object that will get updated as the chain progress. That would let you access the result value whenever suits you (instead of having to add it at the end of the chain).

Instead of a syntax like this:

var result = chainableFunction.doThis().doThat().result;

You would then have:

chainableFunction.update(variableToUpdate).doThis().doThat();
var result = variableToUpdate.result;

The logic is very much the same as the solution proposed by others. Which one to use probably depends on your use cases. A possible issue with having to end the chain with .result is that nothing prevents you from using it this way:

var chainedUp = chainableFunction.doThis().doThat();
doSomething(chainedUp.result);
... 
chainedUp.doOneMoreThing()
... 
doSomething(chainedUp.result);  // oups, .result changed!

With the variableToUpdate option, the result value is not affected by future function calls. Again, that could be desirable in some contexts, not in others.

Full example below

#!/usr/bin/env node

var ChainUp = {};
(function(Class) {

  // Pure functions have no access to state and no side effects
  var Pure = {};
  Pure.isFunction = function(fn) {
     return fn && {}.toString.call(fn) === '[object Function]';
  };

  Class.instance = function() {
    var instance = {};
    var result;
    var updateRef;

    function callBack(fn) {
      // returning a clone, not a reference.
      if(updateRef) { updateRef.r = (result || []).slice(0); } 
      if(Pure.isFunction(fn)) { fn(result); }
    }

    instance.update = function(obj) {
      updateRef = obj;
      return instance;
    };

    instance.one = function(cb) {
        result = (result || []); result.push("one");
        callBack(cb);
        return instance;
      };
      instance.two = function(cb) {
        result = (result || []); result.push("two");
        callBack(cb);
        return instance;
      };
      instance.three = function(cb) {
        result = (result || []); result.push("three");
        callBack(cb);
        return instance;
      };
      instance.result = function() {
        return result;
      };
    return instance;
  };

}(ChainUp));


var result1 = {};
var chain = ChainUp.instance().update(result1);
var one = chain.one(console.log); // [ 'one' ]
console.log(one.result());        // [ 'one' ]
console.log(result1.r);           // [ 'one' ]

var oneTwo = chain.two(); 
console.log(oneTwo.result());  // [ 'one', 'two' ]
console.log(result1.r);        // [ 'one', 'two' ]

var result2 = {};
var oneTwoThree = chain.update(result2).three();
console.log(oneTwoThree.result()); // [ 'one', 'two', 'three' ]
console.log(result2.r);            // [ 'one', 'two', 'three' ]

console.log(result1.r);             // [ 'one', 'two' ]

Note. The Class and instance keywords are probably unfamiliar. That's a convention that I use when using closures instead of prototypical inheritance to construct instances from a prototype. You could replace instance with self (and self = this instead of instance = {})..

like image 2
widged Avatar answered Oct 24 '22 01:10

widged