3

I have a series of chained functions on an object. For example:

var obj = {};
obj.a = function(){
    console.log('in a');  
    return this;
};


obj.b = function(){
    console.log('in b');  
    return this;
};

obj.c = function(){
    console.log('in c'); 
    return this;
};

obj.a().b().c();

Obviously the result when i look at console is:

 in a
 in b
 in c

But how would I break out of this so that a and b execute but c is never reached?

Mark
  • 4,544
  • 10
  • 46
  • 89
  • If you write `c()`, it's going to be executed. A thing you could do is replace the return value of `b` by `{ c: function(){} }` but that's disgusting. – Kyll May 07 '15 at 15:06
  • @OmarElawady that would throw an error – Mark May 07 '15 at 15:07
  • You can't exit from a function if you started it. The function ends at a return statement or at the end of its body. – Emanuel Vintilă May 07 '15 at 15:08
  • The other thing you could do is hack around a way to return an object with the same keys as the current object, but with all members replaced by a dummy `return this` function. Nothing gets executed, just empty stuff calling empty stuff. This is horrendous. Let me code that. – Kyll May 07 '15 at 15:09

4 Answers4

2

The obvious answer is "just don't call c", but I guess that's not an option for some reason.

That aside, you could hack around it by throwing an error from one of the function calls before c, but I wouldn't recommend it. Exceptions are for error handling, not flow control [*].

var obj = {};
obj.a = function(){
    console.log('in a');  
    return this;
};


obj.b = function(){
    console.log('in b');  
    if(shouldBreak) {
      throw new Error('decided to break');
    }

    return this;
};

obj.c = function(){
    console.log('in c'); 
    return this;
};

try {
  obj.a().b().c();
} catch {
  console.log('broke early')
}

[*] unless you're into Python

Community
  • 1
  • 1
joews
  • 27,174
  • 10
  • 70
  • 84
0

Just add a variable outside your methods, say it's called flag, defaulted to true.

Any method that you don't want to be continued after just set that flag to false. Then test flag before returning this.

So something like:

var flag = true

function b() {
    flag = false
    return this
}

function c() {
    return flag ? this : null
}
risingtiger
  • 703
  • 8
  • 18
0

The easiest way is the same way you break out of any function call: throw an error.

var obj = {
    someCondition: false,

    a: function() {
        return this;
    },

    b: function() {
        if (!this.someCondition) {
            var error = new Error("A message");
            error.name = "SomeConditionError";
            throw error;
        }

        return this;
    },

    c: function() {
        return this;
    }
};

And then call the methods and handle the error.

try {
    obj
        .a()
        .b() // throws a "SomeConditionError" here
        .c();
}
catch (error) {
    if (error.name == "SomeConditionError") {
        // gracefully handle "SomeConditionError"
    }
    else {
        // rethrow errors you don't know how to handle gracefully
        throw error;
    }
}

The one thing you want to avoid is using exceptions for flow control.

If you need to call obj.a() then obj.b(), but then conditionally call obj.c() then the calling code needs to handle that:

obj.a().b();

if (someCondition) {
    // Assign to "obj" just in case obj.c() returns a different object
    obj = obj.c();
}

It feels like uglier code (and it is somewhat), but this communicates that any errors thrown in those method calls are catastrophic, show-stopping errors. If you have a complex operation that involves multiple method calls on one object or many objects, consider encapsulating that in a "command":

function DoSomethingCommand(obj) {
    this.obj = obj;
}

DoSomethingCommand.prototype = {
    constructor: DoSomethingCommand,

    execute: function() {
        this.obj.a().b();

        if (someCondition) {
            this.obj = this.obj.c();
        }
    }
};

From the standpoint of the calling code, it's just a simple call to execute() to kick off the really complicated process:

var command = new DoSomethingCommand(obj);

command.execute();
Community
  • 1
  • 1
Greg Burghardt
  • 14,951
  • 7
  • 38
  • 71
-1

Set a class field in a() or b() that is checked by c().

glend
  • 1,208
  • 1
  • 10
  • 23