0

I'm writing a piece of code to easily save error logs in an object for debugging.

What I'm trying to achieve is to get the Object name from the function it was called from like so:

var MainObject = {
    test : function() {
        return MainObject.test.caller;
        // When called from MainObject.testcaller,
        // it should return MainObject.testcaller.
    },

    testcaller : function() {
       return MainObject.test(); // Should return MainObject.testcaller, Returns own function code.
    },

    anothercaller : function() {
       return MainObject.test(); // Should return MainObject.anothercaller, Returns own function code.
    }
}

However when I run this code it returns the function code from MainObject.testcaller.

JSFiddle example

Is there any way this is possible?

Update

After looking at Rhumborl's answer, I discovered that assigning the value through another function would lead it to point back at the function name without the object itself.

Code:

(function (name, func) {
    MainObject[name] = func;
})('invalid', function() {
    return MainObject.test("blah"); 
}); 

// This now points at invalid() rather than MainObject.invalid()

Updated fiddle

3
  • No sensible or robust way, though you could do it by trial and error if the object is referenced by a global variable, or is a property of some other object. But what if there are multiple references? Commented Nov 12, 2014 at 11:29
  • Well, there's arguments.callee.caller but I'm not sure how useful that would be. Commented Nov 12, 2014 at 11:38
  • @Ja͢ck Sadly it gives the same result Commented Nov 12, 2014 at 11:46

2 Answers 2

2

There is a non–standard caller property of functions that returns the caller function, however that is a pointer to a function object and doesn't tell you the object it was called as a method of, or the object's name. You can get a reference to the function through arguments.callee.

There is also the obsolete arguments.caller, but don't use that. It also provides a reference to the calling function (where supported).

Once you have a reference to the calling function (if there is one), you then have the issue of resolving its name. Given that Functions are Objects, and objects can be referenced by multiple properties and variables, the concept of a function having a particular name is alluvial.

However, if you know that the function is a property of some object, you can iterate over the object's own enumerable properties to find out which one it is.

But that seems to be a rather odd thing to do. What are you actually trying to do? You may be trying to solve a problem that can be worked around in a much more robust and simpler way.

Edit

You can do what you want in a very limited way using the method described above for the case in the OP, however it is not robust or a general solution:

var mainObject = {
    test : function() {
      var obj = this;
      var caller = arguments.callee.caller;
      var global = (function(){return this}());
      var fnName, objName;

      for (var p in global) {
        if (global[p] === obj) {
          objName = p;
        }
      }

      for (var f in obj) {
        if (obj[f] === caller) {
          fnName = f;
        }
      }

      return objName + '.' + fnName;
    },

    testcaller : function() {
       return mainObject.test();
    },

    anothercaller : function() {
       return mainObject.test();
    }
}


console.log(mainObject.testcaller());    // mainObject.testcaller
console.log(mainObject.anothercaller()); // mainObject.anothercaller

but it's brittle:

var a = mainObject.anothercaller;
console.log(a()); // mainObject.anothercaller    

var b = {
  foo : mainObject.anothercaller
}

console.log(b.foo()); // mainObject.anothercaller

Oops.

Sign up to request clarification or add additional context in comments.

3 Comments

I'm making a function that logs errors in a way which is simple to read for a developer, since it is part of a larger project. For instance if a function lacks certain requirement, the error function is called, logs the function to an array and when a customer would send a bug report on the page, it sends the error reports in the background, making it very easy to see what exactly caused the fault.
hmm doesn't quite work for me in jsfiddle: Chrome gives a security error, FF returns 'undefined.testcaller'
It "works" for me in Safari and Firefox. jsfiddle is a world unto itself (or should I say "an awesomeness all of its own"). It will not work at all in strict mode (callee is not allowed). The code is only there as an example that it can be done in a limited case, it certainly should not be used as a general or even cross browser solution.
1

You can use this trick at http://www.eriwen.com/javascript/js-stack-trace/ which throws an error, then parses the stack trace.

I have updated it for the latest versions of Firefox, Chrome and IE. Unfortunately it doesn't work well on my IE9 (and I haven't tested it on Opera).

function getStackTrace() {
    var callstack = [];
    var isCallstackPopulated = false;
    try {
        i.dont.exist += 0; //doesn't exist- that's the point
    } catch (e) {
        if (e.stack) { //Firefox/Chrome/IE11
            var lines = e.stack.split('\n');
            for (var i = 0, len = lines.length; i < len; i++) {
                var line = lines[i].trim();
                if (line.match(/^at [A-Za-z0-9\.\-_\$]+\s*\(/)) {
                    // Chrome/IE: "    at Object.MainObject.testcaller (url:line:char)"
                    var entry = line.substring(3, line.indexOf('(') - 1);
                    // Chrome appends "Object." to the front of the object functions, so strip it off
                    if (entry.indexOf("Object.") == 0) {
                        entry = entry.substr(7, entry.length);
                    }
                    callstack.push(entry);
                } else if (line.match(/^[A-Za-z0-9\.\-_\$]+\s*@/)) {
                    // Firefox: "MainObject.testcaller@url:line:char"
                    callstack.push(line.substring(0, lines[i].indexOf('@')));
                }
            }
            //Remove call to getStackTrace()
            callstack.shift();
            isCallstackPopulated = true;
        } else if (window.opera && e.message) { //Opera
            var lines = e.message.split('\n');
            for (var i = 0, len = lines.length; i < len; i++) {
                if (lines[i].match(/^\s*[A-Za-z0-9\-_\$]+\(/)) {
                    var entry = lines[i];
                    //Append next line also since it has the file info
                    if (lines[i + 1]) {
                        entry += lines[i + 1];
                        i++;
                    }
                    callstack.push(entry);
                }
            }
            //Remove call to getStackTrace()
            callstack.shift();
            isCallstackPopulated = true;
        }
    }
    if (!isCallstackPopulated) { //IE9 and Safari
        var currentFunction = arguments.callee.caller;
        while (currentFunction) {
            var fn = currentFunction.toString();
            var fname = fn.substring(fn.indexOf("function") + 8, fn.indexOf('')) || 'anonymous';
            callstack.push(fname);
            currentFunction = currentFunction.caller;
        }
    }
    return callstack;
}

var MainObject = {
    test: function (x) {
        // first entry is the current function (test), second entry is the caller
        var stackTrace = getStackTrace();
        var caller = stackTrace[1];
        return caller + "()";
    },

    testcaller: function () {
        return MainObject.test(1, null);
    }
}

function SomeFunction() {
  return MainObject.test("blah");
}

document.body.innerHTML += '<b style="color: red">' + MainObject.testcaller() + '</b>';

document.body.innerHTML += '<div>Calling SomeFunction() returns: <b style="color: red">' + SomeFunction() + '</b></div>';
MainObject.test() should return: <b style="color: blue">MainObject.testcaller()</b>
<hr />
MainObject.test() returns:

Updated fiddle here

2 Comments

After playing around with your code for a little bit, It appears that when the function is set through another function, it points to the function name itself without the Object name, is there any way to fix this? (Check my updated answer)
I don't think so - even Chrome Dev Tools just labels it as (anonymous function) in the Call Stack

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.