Get object caller name by function call JavaScript - javascript

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

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.

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

Related

Variable scope in functions

I have a SCOPE problem. When I declare "var text" outside the function all works. But inside the function it works only in the first part. Here is what I mean:
This is a buffer function. Executing buffer("anything") saves "anything". Executing buffer() - without the properties will return all properties.
buffer("Al")
buffer("ex")
buffer() <= should return Alex
But the SCOPE of "text" is wrong and it does not return the saved properties.
function makeBuffer() {
var text = "";
if (arguments.length != 0) {
for (let i = 0; i < arguments.length; i++) {
console.log(`Adding argument - (${arguments[i]})`);
text += arguments[i];
console.log(`New text - (${text})`);
}
} else {
console.log(`text - (${text})`);
return text;
}
}
var buffer = makeBuffer;
buffer("One", "Two");
document.write(buffer());
That is normal behaviour.
A variable defined in a given scope goes away when the scope goes away. Each call the to the function creates a new scope.
Declaring the variable outside the function is the standard way to share a value between invocations of it.
What you want is a factory:
function makeBuffer() {
var text = "";
return function buffer() {
if (arguments.length != 0) {
for (let i = 0; i < arguments.length; i++) {
console.log(`Adding argument - (${arguments[i]})`);
text += arguments[i];
console.log(`New text - (${text})`);
}
} else {
console.log(`text - (${text})`);
return text;
}
}
}
var buffer = makeBuffer();
buffer("One", "Two");
document.write(buffer());
You could do this using an object. This will make your code much more organized.
var Buffer = function() {
this.text = "";
}
Buffer.prototype.append = function() {
for (var i = 0; i < arguments.length; i++) {
this.text += arguments[i];
}
}
Buffer.prototype.get = function() {
return this.text;
}
var buffer = new Buffer();
buffer.append("One", "Two");
document.write(buffer.get());
Using ES6 the syntax gets even sweeter:
class Buffer {
constructor() {
this.text = "";
}
append() {
this.text = this.text.concat(...arguments);
}
get() {
return this.text;
}
}
var buffer = new Buffer();
buffer.append("One", "Two");
document.write(buffer.get());
As Quentin properly pointed out in his answer, that is a normal behavior.
An alternative option to keep the value on your function without declaring the variable outside is scope is to add it as a property to the function itself.
As in JavaScript a function is a first class object, you can put such data directly into function object (like in any other objects).
An example below, please note how to get property text from your function (buffer.text).
function makeBuffer() {
makeBuffer.text = "";
if (arguments.length != 0) {
for (let i = 0; i < arguments.length; i++) {
console.log(`Adding argument - (${arguments[i]})`);
makeBuffer.text += arguments[i];
console.log(`New text - (${makeBuffer.text})`);
}
} else {
console.log(`text - (${makeBuffer.text})`);
return makeBuffer.text;
}
}
var buffer = makeBuffer;
buffer("Al", "ex");
console.log(`buffer.text - (${buffer.text})`);
Alternatively consider using a closure in order to keep the value of text between function calls.
Closures are functions that refer to independent (free) variables
(variables that are used locally, but defined in an enclosing scope).
In other words, these functions 'remember' the environment in which
they were created. More info here.
let makeBuffer = function() {
// closure
let text = "";
return function() {
if (arguments.length != 0) {
for (let i = 0; i < arguments.length; i++) {
console.log(`Adding argument - (${arguments[i]})`);
text += arguments[i];
console.log(`New text - (${text})`);
}
} else {
console.log(`text - (${text})`);
return text;
}
}
};
var buffer = makeBuffer();
buffer("Al", "ex");

Rolling my own Require

Forward
It has come to my attention that this problem was a "Function Scoping - Not Block Scoping" answer, the difference being for ... in opposed to for ... i<n ... i++ The solution could be wrapping the for(var p in ...) { with a function to give them their own scope (much like was done with Array.prototype.forEach). Thank you for your help.
Problem
I'm rolling my own tiny Require.js solution as opposed to the actual Require.js library (no I haven't taken a look at their source).
My callback function never seems to be executed, but I can't figure out why. It's probably a simple logical error, but you know how it goes when you stare at your own code for too long. (Everything looks logically sound)
Usage
Used as follows:
require(["LibraryA", "LibraryB", "LibraryC"], function() {
//code which requires libraries a-c here
});
Code
var require = (function() {
var scriptsLoaded = [];
return function(paths, callback) {
var pathsDoneLoading = {};
for(var i = 0; i < paths.length; i++) {
pathsDoneLoading[paths[i]] = false;
}
for(var p in pathsDoneLoading) {
for(var i = 0; i < scriptsLoaded.length; i++) {
if(p === scriptsLoaded[i]) {
pathsDoneLoading[p] = true;
}
}
}
for(var p in pathsDoneLoading) {
if(pathsDoneLoading[p] === false) {
var script = document.createElement("script");
script.src = p + ".js";
script.onload = function() {
scriptsLoaded.push(p);
pathsDoneLoading[p] = true;
for(var p in pathsDoneLoading) {
if(pathsDoneLoading[p] !== true) {
return;
}
}
callback();
};
document.documentElement.appendChild(script);
}
}
}
})();
Plunker
https://plnkr.co/edit/OFWTUpmV3sIJAjhGhcLO
It's the old for loop variable catching problem.
During your loop where it's iterating through what paths are loading (for (var p in pathsDoneLoading) {) the p is being bound to the function inside. The problem is that p changes and persists after the loop has complete so every single function is adding the last key in the pathsDoneLoading object to the scriptsLoaded array (and setting the matching value in pathsDoneLoading to true).
An example of the problem:
var obj = { a: 1, b: 2, c: 3 };
var funcs = [];
for (var key in obj) {
funcs.push(function() {
return key; // Remember, `key` is bound to the function, not the block
});
}
funcs.forEach(function(f) {
document.querySelector('pre').innerText += f() + '\n';
});
<pre></pre>
To fix this, you can wrap it in an IIFE to create a new binding per p value.
for (var p in pathsDoneLoading) {
(function(p) {
if (pathsDoneLoading[p] === false) { // or `if (!pathsDoneLoading[p])`
...
})(p);
}
That way every time you generate an onload handler, they have a unique binding to the intended path.
Our previous example fixed with this solution:
var obj = { a: 1, b: 2, c: 3 };
var funcs = [];
for (var key in obj) {
(function(key) {
funcs.push(function() {
return key; // Remember, `key` is bound to the function, not the block
});
})(key);
}
funcs.forEach(function(f) {
document.querySelector('pre').innerText += f() + '\n';
});
<pre></pre>
Simply put, you'll need capture the for loop iterator because it's a moving target and by the time onload fires, it will have changed.
var require = (function() {
var scriptsLoaded = [];
return function(paths, callback) {
var pathsDoneLoading = {};
for(var i = 0; i < paths.length; i++) {
pathsDoneLoading[paths[i]] = false;
}
for(var p in pathsDoneLoading) {
for(var i = 0; i < scriptsLoaded.length; i++) {
if(p === scriptsLoaded[i]) {
pathsDoneLoading[p] = true;
}
}
}
for(var p in pathsDoneLoading) {
if(pathsDoneLoading[p] === false) {
var script = document.createElement("script");
script.src = p + ".js";
// Wrapping in a closure
script.onload = (function(Vp) {
scriptsLoaded.push(Vp);
pathsDoneLoading[Vp] = true;
for(var prop in pathsDoneLoading) {
if(pathsDoneLoading[prop] !== true) {
return;
}
}
callback();
// Execute the closure function and send in your
// var(s) as argument(s) to capture it's current state
}(p));
document.documentElement.appendChild(script);
}
}
}
})();

JavaScript - reactivity

I have this code. It does that always when there is function, that is interested on some key in Session, it will be called whenever the key changed its value.
The problem is that to track what keys the function is interested, i need to run that function once and it can have some sideffects (if that function will manipulate DOM for instance). How can i run that function without affecting the current environment, if it is possible....?
var checkRunning = false;
var keys = [];
var checks = {};
var Session = {
get: function (key) {
if (checkRunning) {
keys.push(key);
}
},
set: function (key, value) {
if (checks[key]) {
var l = checks[key].lenght;
for (var i = 0; i < l; i++) {
checks[key][i]();
}
}
}
};
function check(f) {
checkRunning = true;
f();
checkRunning = false;
var l = keys.lenght;
for (var i = 0; i < l; i++) {
if (checks[keys[i]]) {
checks[keys[i]].push(f);
}
else {
checks[keys[i]] = [f];
}
}
keys = [];
}
//how to use
var a = "something";
check(function () {
// this function should be run always when Session key "a_dep" will change
a = Session.get("a_dep");
});
Session.set("a_dep", 10);
Session.set("a_dep", 20);
So I refactored your code to achieve what I understood you wanted.
I first redefined Session and its getter/setter. I store the values in a literal object with the key to access it :
function Session () {
this.myDictionnary = {};
this.myCallbacks = {};
}
Session.prototype.set = function (key, value) {
this.myDictionnary[key] = value;
}
Session.prototype.get = function (key) {
return this.myDictionnary[key];
}
Here we have our core code. Now you want to call a function every time you set a value to a key, so we have to
Set a function (or function set) - key couple
Call this function (or these functions) when a value is set to key
Note the myCallbacks literal object which will handle these couples. So first, create the key - function couple :
Session.prototype.callbackWhenSet = function (key, callback) {
if(!this.myCallbacks[key]) {
this.myCallbacks[key] = [];
}
this.myCallbacks[key].push(callback);
}
Then call it when set is called (here I rewrite the set function )
Session.prototype.set = function (key, value) {
this.myDictionnary[key] = value;
if(this.myCallbacks[key]) {
for(var i = 0; i < this.myCallbacks[key].length; i++) {
this.myCallbacks[key][i]();
}
}
}
Finally we can test it !
var test = new Session();
test.callbackWhenSet("a_dep", function () {
alert("a_dep is set to " + test.get("a_dep"));
});
test.set("a_dep", 10);
test.set("something", 250);
test.set("a_dep", 20);
As you can see here, we have an alert showed each time "a_dep" is set.
Or you can use ProAct.js and do stuff like this:
var obj = ProAct.prob({
a: 4
});
obj.p('a').on(function () {
console.log('obj.a has changed!');
});

Mutable variable is accessible from closure. How can I fix this?

I am using Typeahead by twitter. I am running into this warning from Intellij. This is causing the "window.location.href" for each link to be the last item in my list of items.
How can I fix my code?
Below is my code:
AutoSuggest.prototype.config = function () {
var me = this;
var comp, options;
var gotoUrl = "/{0}/{1}";
var imgurl = '<img src="/icon/{0}.gif"/>';
var target;
for (var i = 0; i < me.targets.length; i++) {
target = me.targets[i];
if ($("#" + target.inputId).length != 0) {
options = {
source: function (query, process) { // where to get the data
process(me.results);
},
// set max results to display
items: 10,
matcher: function (item) { // how to make sure the result select is correct/matching
// we check the query against the ticker then the company name
comp = me.map[item];
var symbol = comp.s.toLowerCase();
return (this.query.trim().toLowerCase() == symbol.substring(0, 1) ||
comp.c.toLowerCase().indexOf(this.query.trim().toLowerCase()) != -1);
},
highlighter: function (item) { // how to show the data
comp = me.map[item];
if (typeof comp === 'undefined') {
return "<span>No Match Found.</span>";
}
if (comp.t == 0) {
imgurl = comp.v;
} else if (comp.t == -1) {
imgurl = me.format(imgurl, "empty");
} else {
imgurl = me.format(imgurl, comp.t);
}
return "\n<span id='compVenue'>" + imgurl + "</span>" +
"\n<span id='compSymbol'><b>" + comp.s + "</b></span>" +
"\n<span id='compName'>" + comp.c + "</span>";
},
sorter: function (items) { // sort our results
if (items.length == 0) {
items.push(Object());
}
return items;
},
// the problem starts here when i start using target inside the functions
updater: function (item) { // what to do when item is selected
comp = me.map[item];
if (typeof comp === 'undefined') {
return this.query;
}
window.location.href = me.format(gotoUrl, comp.s, target.destination);
return item;
}
};
$("#" + target.inputId).typeahead(options);
// lastly, set up the functions for the buttons
$("#" + target.buttonId).click(function () {
window.location.href = me.format(gotoUrl, $("#" + target.inputId).val(), target.destination);
});
}
}
};
With #cdhowie's help, some more code:
i will update the updater and also the href for the click()
updater: (function (inner_target) { // what to do when item is selected
return function (item) {
comp = me.map[item];
if (typeof comp === 'undefined') {
return this.query;
}
window.location.href = me.format(gotoUrl, comp.s, inner_target.destination);
return item;
}}(target))};
I liked the paragraph Closures Inside Loops from Javascript Garden
It explains three ways of doing it.
The wrong way of using a closure inside a loop
for(var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
Solution 1 with anonymous wrapper
for(var i = 0; i < 10; i++) {
(function(e) {
setTimeout(function() {
console.log(e);
}, 1000);
})(i);
}
Solution 2 - returning a function from a closure
for(var i = 0; i < 10; i++) {
setTimeout((function(e) {
return function() {
console.log(e);
}
})(i), 1000)
}
Solution 3, my favorite, where I think I finally understood bind - yaay! bind FTW!
for(var i = 0; i < 10; i++) {
setTimeout(console.log.bind(console, i), 1000);
}
I highly recommend Javascript garden - it showed me this and many more Javascript quirks (and made me like JS even more).
p.s. if your brain didn't melt you haven't had enough Javascript that day.
You need to nest two functions here, creating a new closure that captures the value of the variable (instead of the variable itself) at the moment the closure is created. You can do this using arguments to an immediately-invoked outer function. Replace this expression:
function (item) { // what to do when item is selected
comp = me.map[item];
if (typeof comp === 'undefined') {
return this.query;
}
window.location.href = me.format(gotoUrl, comp.s, target.destination);
return item;
}
With this:
(function (inner_target) {
return function (item) { // what to do when item is selected
comp = me.map[item];
if (typeof comp === 'undefined') {
return this.query;
}
window.location.href = me.format(gotoUrl, comp.s, inner_target.destination);
return item;
}
}(target))
Note that we pass target into the outer function, which becomes the argument inner_target, effectively capturing the value of target at the moment the outer function is called. The outer function returns an inner function, which uses inner_target instead of target, and inner_target will not change.
(Note that you can rename inner_target to target and you will be okay -- the closest target will be used, which would be the function parameter. However, having two variables with the same name in such a tight scope could be very confusing and so I have named them differently in my example so that you can see what's going on.)
In ecmascript 6 we have new opportunities.
The let statement declares a block scope local variable, optionally initializing it to a value.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let
Since the only scoping that JavaScript has is function scope, you can simply move the closure to an external function, outside of the scope you're in.
Just to clarify on #BogdanRuzhitskiy answer (as I couldn't figure out how to add the code in a comment), the idea with using let is to create a local variable inside the for block:
for(var i = 0; i < 10; i++) {
let captureI = i;
setTimeout(function() {
console.log(captureI);
}, 1000);
}
This will work in pretty much any modern browser except IE11.

Nodejs calling function from within a function

I have 3 methods
exports.getImageById = function (resultFn, id) {
...
}
exports.getCollectionById = function (resultFn, id) {
}
in the third method I want to call both methods
exports.getCollectionImages = function (resultFn, collectionId) {
var arr = new Array();
this.getCollectionById( // fine, 1st call
function (result) {
var images = result.image;
for (i = 0; i < images.length; i++) {
this.getImageById(function (result1) { // error, 2nd call
arr[i] = result1;
}, images[i]
);
}
}
, collectionId
);
resultFn(arr);
}
I can call first function this.getCollectionById but it fails to call this.getImageById, it says undefined function, whats the reason for that?
When you call this.getCollectionById passing it a callback, the callback doesn't have access to the same this
The simplest solution is to save this as a local variable.
exports.getCollectionImages = function (resultFn, collectionId) {
var arr = new Array();
var me = this; // Save this
this.getCollectionById( // fine, 1st call
function (result) {
var images = result.image;
for (var i = 0; i < images.length; i++) {
// Use me instead of this
me.getImageById(function (result1) { // error, 2nd call
arr[i] = result1;
}, images[i]);
}
}, collectionId);
resultFn(arr);
}
The value of this inside the inner function is not the same object as outside, because it's determined depending on how the function is called. You can find a detailed explanation in the MDN article on this.
One of the ways to solve it is by keeping a reference to the outer this in another variable such as that:
var that = this;
this.getCollectionById( // fine, 1st call
function (result) {
var images = result.image;
for (i = 0; i < images.length; i++) {
that.getImageById(function (result1) { // 2nd call
arr[i] = result1;
}, images[i]
);
}
}
, collectionId
);

Categories