7

In random.function("blah").check(), how do I make it ignore check() if function("blah") returns false? Or is this even possible?

The background:

My entire project is based around objects and plugins. These plugins are additional .js files that contain an object. for example, afk.js looks something like this:

global.afk = function(){};
afk.check = function(){ ... };

etc, etc. And by loading that js file, I get access to the afk object and all the function therein. Probably not efficient, but it works and it makes figuring out what functions are doing somewhat easier.

Because I don't know which plugins are loaded, in some of the shared core .js files, there are these conditional statements:

if (plugin.has("afk")) { afk.check(); };

But I then decided to have plugin.has() return the actual object instead of 'true', so I could do:

plugin.has("afk").check();

which works as expected. Unless there is no afk plugin, which throws the obvious error of "false has no method check".

Can this error be avoided in an elegant way?

5
  • Check out the source of jQuery... :) Commented Apr 15, 2013 at 17:29
  • 1
    @PraveenKumar I have been, but I can't seem to find out which bit they use. I get lost in jQuery sometimes haha. Commented Apr 15, 2013 at 17:32
  • I voted your question up... I too have the same question and in the same situation, got lost inside jQuery code... Seriously they rock man! :P Commented Apr 15, 2013 at 17:34
  • 1
    It's not possible. Instead of returning false, you should keep track of the status internally and decide based on that what the check method should be doing. Commented Apr 15, 2013 at 17:38
  • 1
    @FelixKling Well, this isn't just for check. This is for any number of functions doing any number of things. I was thinking of switching plugin.has() to do plugin.has("afk","check"); or something similar, so instead of chaining the functions, it's just in one function. But passing variables to the check function would get tricky. Commented Apr 15, 2013 at 17:42

9 Answers 9

1

The returned object should have that function, otherwise you get an error. What you can do is make a base Object type, which is always returned. Than when you return it, it has an empty function and will not create an error.

Something like:

var baseObj = function(){
    this.afk = function(){
        return this;
    }
    this.check = function(){
        return false;
    }
};

var advObj = new baseObj();
advObj.afk = function(){
    // extended func
    return new baseObj();
}

var a = new advObj();
a.afk("test").check();
Sign up to request clarification or add additional context in comments.

3 Comments

So with this, for every function I add, I'll have to add a similar function to a blank, empty object, and return that object so the line doesn't break? That blank object would have to be huge. I have a lot of plugins. :P
This is, of course, going to need a dummy function for every function the real class supports, which sounds kind of impractical. Some kind of dummy class that overrides call() and apply() to dummy out every possible function name would be ideal, if that's possible.
You don't exactly know what will be the chained function. jQuery itself is a extended Array with custom functions. Which will be extended with all the $.fn functions. And therefore, the object which is return is always this, that object contains all the plugins which are in the project. And therefore will not crash.
1

So jQuery does it a bit like this:

$(plugin) -> returns a collection like object
.has("afk") -> filters the internal collection, and returns the collection
.check() -> action executed on this collection

So let's make a small example:

function Wrapper(ele) {
  // make our collection
  this.coll = [ ele ];

  // function to filter
  this.has = function(filter) {
    this.coll = this.coll.filter(function(e) {
      return e === filter;
    });

    // we return ourself for chaining
    return this;
  };

  // function to execute on ourself
  this.check = function() {
    // returns actual value
    return this.coll.length;
  };
}

var $ = function(ele) {
  // wrapper around the `new` syntax
  return new Wrapper(ele);
};

console.log($('jan').has('jan').check()); // gives 1
console.log($('pietje').has('jan').check()); // gives 0

Cool thing is you can now extend your Wrapper object from external files by extending the prototype:

Wrapper.prototype.hasNot = function(filter) { 
    /* do magic */
    return this;
}

3 Comments

But again, this looks like I would have to code a global object that contains a version of every function in every plugin file. I'm beginning to think that what I want to do just isn't possible.
well, I still have to have a global object, I can just modify it from external files which only makes it slightly easier. But that's still no more efficient than a true/false function and doing if (plugin.has("afk")) { afk.check(); };
Hmm... I'm not sure, but you just might be able to do something crazy with Function.prototype.constructor to automatically add a new dummy function to your global object every time a function is defined anywhere. Have to watch out for infinite loops, though.
1

A short and dirty way to make this work would be to use the && operator.

var x = {};

x.afk && x.afk();

x.afk = function () { console.log("I exist!"); };

x.afk && x.afk();

Not your optimal wide range solution though.

2 Comments

My code is already this, essentially. I do use the && and || operators extensively, I just used the if statements as an easier to read example. After plugin.has and afk.check are defined, the code that runs is: plugin.has("afk") && afk.check(); which is really nice and easy and all, I just think the way I described would be nicer and easier and more aesthetically pleasing to read.
Someone else mentioned overriding or extending apply() and call() That could be your best bet, that or implementing your own.
1

Within current ECMAScript specifications, turning .check() into a no-op (and any other method that might exist on the non-existent plugin) is impossible since you cannot as you've found portably access undefined properties of undefined objects.

The proposed "Direct Proxies" ECMAScript API would appear to solve this, but it's not standardised yet. I also suspect that it's not very efficient to proxy your code this way since you would (AIUI) have to make your .has() function return a trapping proxy object which then has to do real work for every "missed" function call. It would be far better IMHO to simply ensure that the conditional code is never run at all.

So, other than the && method proposed elsewhere, all I can offer is this:

plugin.if("afk", function() {
    // put conditional code here
});

Comments

0

It won't work in all browsers, but I think you could create a dummy class that uses __noSuchMethod__ to always return itself instead of throwing an error when a nonexistent method is called. See this question.

I do not know what browsers this will work in, and my general impression is "not many".

3 Comments

I figure I'll give this a shot (I'm running this as a program in node.js, so browser compatibility isn't really an issue for me :P)
Okay, so plugin.has returns the object on true, and on false, returns plugin.blank which is this: plugin.blank = { __noSuchMethod__ : function(a,b) { return; } }; however, I still get "has no method check" :D
that'll be because V8 doesn't support __noSuchMethod__ - it's non-standard.
0

Oh, hello. Node-proxy looks promising. I don't actually know anything about it, but it sounds like it's supposed to let you do exactly this kind of thing.

Comments

0

Actually you solved the problem already yourself, at least almost:

This is for any number of functions doing any number of things. I was thinking of switching plugin.has() to do plugin.has("afk","check"); or something similar, so instead of chaining the functions, it's just in one function. But passing variables to the check function would get tricky.

Well, there are basically two ways for this:

plugin_with_afk.has("afk","check").check(100); // "check" twice
plugin_with_afk.has("afk","check")(100); // "check" not twice

The first way is actually rather simple: simply return this if the plugin has "afk", and otherwise return an object that has a no-op method check:

Plugin.prototype.has = function(str, member){
    if(this.things.indexOf(str) > -1) {
        return this;
    } else {
        var tmp = {}
        tmp[member] = function(){}
        return tmp;
    }
}

plugin_with_afk.has("afk","check").check(1);     // has afk, check is there
plugin_without_afk.has("afk","check").check(2);  // has no afk, check is no-op
plugin_without_afk.has("nope","check").check(3); // has nope, check is there

This has also the great advantage that you don't need to use any function wrapper and so on. However, you have to specify the used function twice, which would result in ReferenceError if you accidentally used another name:

plugin_with_afk.has("afk","check").test(1); // oh oh

So what can you do against this? Create a simple function wrapper:

PluginWithFuncRet.prototype.has = function(str,member){
    var self = this;
    if(this.things.indexOf(str) > -1) {
        return function(){
            return self[member].apply(self, arguments);
        }
    } else {
        return function(){};
    }
}

plugin_with_afk.has("afk","check")(4);   // calls check()
plugin_without_afk.has("afk","check")(5);// no-op

And that's it. Note that you should actually rename has to use_if or something similar, since it's now doing many things more than actually checking whether a component is there.

Complete code (demo)

var PluginBase = function(name, things){
    this.name = name;
    this.things = things;
}

/// First variant
var Plugin = function() {
    this.constructor.apply(this, arguments);
}
Plugin.prototype.constructor = PluginBase;

Plugin.prototype.has = function(str,member){
    if(this.things.indexOf(str) > -1) {
        return this;
    } else {
        var tmp = {}
        tmp[member] = function(){}
        return tmp;
    }
}

var plugin_with_afk = new Plugin("with afk", ["afk"]);
plugin_with_afk.check = function(val){
    console.log("hi", val, this.name);
};

var plugin_without_afk = new Plugin("w/o afk", ["nope"]);
plugin_without_afk.check = function(val){
    console.log("nope", val, this.name);
}
/// First variant demo

plugin_with_afk.has("afk","check").check(1)
plugin_without_afk.has("afk","check").check(2)
plugin_without_afk.has("nope","check").check(3)

/// Alternative
var PluginWithFuncRet = function(){
    this.constructor.apply(this, arguments);
}
PluginWithFuncRet.prototype.constructor = PluginBase;

PluginWithFuncRet.prototype.has = function(str,member){
    var self = this;
    if(this.things.indexOf(str) > -1) {
        return function(){
            return self[member].apply(self,arguments);
        }
    } else {
        return function(){}
    }
}

plugin_with_afk    = new PluginWithFuncRet("with afk",["afk"]);
plugin_with_afk.check = function(val){
    console.log("Hi",val,this.name);
}
plugin_without_afk    = new PluginWithFuncRet("w/o afk",["nope"]);
plugin_without_afk.check = function(val){
    console.log("Nope",val,this.name);
}

/// Alternative demo
plugin_with_afk.has("afk","check")(4);
plugin_without_afk.has("afk","check")(5);
plugin_without_afk.has("nope","check")(6);

Result:

hi 1 with afk
nope 3 w/o afk
Hi 4 with afk
Nope 6 w/o afk

Comments

0

So this is what I came up with, and eventually ended up using.
Wrather than create a dummy wrapper, I decided to load all the
plugins into the main wrapper. So now, afk is plg.afk, as with
all the other plugins. That allowed me to write this wrapper:

global.& = function(a,b) {
  var x,y,z;if (!a) return false; //if nothing, return false.
  if (a.indexOf(".") < 0) { //if value has no "."
    if (plg["data"][a]) { x = ["data",a]; } //use data function if exists
    else { x = ["basic",a]; } //else assume it's a basic function
  } else { //or if it does contain "." (such as my example, afk.check)
    x = a.split(".") //define the plugin, and the function as an array.
  }
  y = x[0],z = x[1]; //define the plugin,function as variables.
  if (!plg[y]) { return false; } //return if plugin doesn't exist.
  if (!plg[y][z]) { return false; } //return if function doesn't exist.
  if (!b) { plg[y][z](); } //if no variable, call raw function
  return plg[y]z[](b); //return function, passing an optional variable.
}

So this works! I can call $("afk.check"); And it essentially does:

if (plugin.has("afk") && afk.hasOwnProperty("check")) { afk.check(); };

:D

And I can pass variables., and use a shortcut for basic functions.

For example, if I call $("boot"); then it checks data for a boot function, and when it doesn't find one, reverts to basic.boot();, and calls the right function. And I can call data functions and pass variables such as in the case of saving data, $("save",data);

Feel free to let me know if this is a stupid idea! But so far it's working for me <3

7 Comments

This is a stupid idea because you'll end up with an unmaintainable mess of code. You're basically reimplementing eval(). Also I don't see how you would be able to chain commands here.
@JanJongboom it was less about chaining commands, and more about being able to check if a function existed before calling it. :$
In that case a plugin.afk && plugin.afk() is a way nicer solution imho that doesn't involve calling wrapper functions with magic strings, then splitting the strings and calling magic, or anything that smells like eval :-)
It's also a pretty standard pattern.
Yeah, I made a joke that this was all about me procrastinating, and it's starting to look like it wasn't so much of a joke. I was looking for an easier method and I already had basically the easiest. Thanks. I wonder if I can delete questions...
|
0

Now many years later, JavaScript has the optional chaining operator (?.).

To use it in the described scenario, where your function might return false, use it just before the parentheses with which the non-existing method would be executed:

plugin.has("afk").check?.();

Here, no error will occur when plugin.has("afk") returns false.

However, a function that either returns an object or false is not really the best practice: in the above solution, false is first wrapped into a Boolean instance, from which the check property is retrieved -- which turns out to be undefined, and that is where the chain stops. Better practice is to return null instead of false, as null represents the intentional absence of any object value. This also has the advantage that the chain is aborted one step earlier:

plugin.has("afk")?.check();

Now there is not even an attempt to access check, and no object wrapping occurs. The requirement is that the has function returns null (or undefined) when it doesn't return an object with a check method.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.