20

For instance this code:

function stuff() {
  this.onlyMethod = function () {
    return something;
  }
}

// some error is thrown
stuff().nonExistant();

Is there a way to do something like PHP's __call as a fallback from inside the object?

function stuff() {
  this.onlyMethod = function () {
    return something;
  }
  // "catcher" function
  this.__call__ = function (name, params) {
    alert(name + " can't be called.");
  }
}

// would then raise the alert "nonExistant can't be called".
stuff().nonExistant();

Maybe I'll explain a bit more what I'm doing.

The object contains another object, which has methods that should be accessible directly through this object. But those methods are different for each object, so I can't just route them, i need to be able to call them dynamically.

I know I could just make the object inside it a property of the main object stuff.obj.existant(), but I'm just wondering if I could avoid it, since the main object is sort of a wrapper that just adds some functionality temporarily (and makes it easier to access the object at the same time).

Tor Valamo
  • 33,261
  • 11
  • 73
  • 81

4 Answers4

18

Well, it seems that with harmony (ES6), there will be a way, and it's more complicated compared to the way other programing languages do it. Basically, it involves using the Proxy built-in object to create a wrapper on the object, and modify the way default behavior its implemented on it:

obj  = new Proxy({}, 
        { get : function(target, prop) 
            { 
                if(target[prop] === undefined) 
                    return function()  {
                        console.log('an otherwise undefined function!!');
                    };
                else 
                    return target[prop];
            }
        });
obj.f()        ///'an otherwise undefined function!!'
obj.l = function() {console.log(45);};
obj.l();       ///45

The Proxy will forward all methods not handled by handlers into the normal object. So it will be like if it wasn't there, and from proxy you can modify the target. There are also more handlers, even some to modify the prototype getting, and setters for any property access yes!.

As you would imagine, this isn't supported in all browsers right now, but in Firefox you can play with the Proxy interface quite easy, just go to the MDN docs

It would make me happier if the managed to add some syntactic sugar on this, but anyway, its nice to have this kind of power in an already powerful language. Have a nice day! :)

PD: I didn't copy rosettacode js entry, I updated it.

Minifyre
  • 510
  • 2
  • 5
  • 17
Nicolas NZ
  • 368
  • 3
  • 5
  • 1
    But this is useless as it doesn't work on the target object itself, but on a brand new object. In other words, you can't use this to catch `Array`'s property access. – Pacerier Nov 23 '17 at 09:02
  • @Pacerier Just because it doesn't work for your use case doesn't make it useless. In fact, this is useful for the question at hand. – Ruben Martinez Jr. Nov 11 '21 at 23:35
  • @Pacerier your use case may be valid, but i doubt, if you are writing a library, which could be a valid one, then give a new "MyArray" object to the consumer, instead of modifying the built in ones, this increases readability and keeps the system decoupled. Also, there *could* be performance issues by making built-in's less predictable. – Nicolas NZ Nov 15 '21 at 23:43
10

There is a way to define a generic handler for calls on non-existant methods, but it is non-standard. Checkout the noSuchMethod for Firefox. Will let you route calls to undefined methods dynamically. Seems like v8 is also getting support for it.

To use it, define this method on any object:

var a = {};

a.__noSuchMethod__ = function(name, args) {
    console.log("method %s does not exist", name);
};

a.doSomething(); // logs "method doSomething does not exist"

However, if you want a cross-browser method, then simple try-catch blocks if the way to go:

try {
    a.doSomething();
}
catch(e) {
    // do something
}

If you don't want to write try-catch throughout the code, then you could add a wrapper to the main object through which all function calls are routed.

function main() {
    this.call = function(name, args) {
        if(this[name] && typeof this[name] == 'function') {
            this[name].call(args);
        }
        else {
            // handle non-existant method
        }
    },
    this.a = function() {
        alert("a");
    }
}

var object = new main();
object.call('a') // alerts "a"
object.call('garbage') // goes into error-handling code
Anurag
  • 140,337
  • 36
  • 221
  • 257
  • trycatch doesn't work because it cuts the flow of the user code ("How would you resume operation?") and secondly, theres no way to access the obj from the catch handler. – Pacerier Sep 13 '17 at 07:48
  • *`This feature is obsolete... Try to avoid using it.`* – vsync Sep 18 '18 at 12:09
2

It seems that you know your way around JS. Unfortunately, I don't know of such feature in the language, and am pretty sure that it does not exist. Your best option, in my opinion is either using a uniform interface and extend it, or extend the prototypes from which your objects inherit (then you can use instanceof before going forward with the method call) or use the somewhat cumbersome '&&' operator in order to avoid the access of nonexistent properties/methods:

obj.methodName && obj.methodName(art1,arg2,...);

You can also extend the Object prototype with Anurag's suggestion ('call').

MasterAM
  • 16,283
  • 6
  • 45
  • 66
1

You can also check if the method exists.

if(a['your_method_that_doesnt_exist']===undefined){
//method doesn't exist
}
fmsf
  • 36,317
  • 49
  • 147
  • 195
  • That's not the point. The reality is probably that you would like to catch such undefined methods and do some something instead of dumping an error. for example, return some default value instead – vsync Sep 18 '18 at 12:11
  • 1
    Anyway, it's better to do `if( methodName in a )`.. to check if the method exists – vsync Sep 18 '18 at 12:12