Why is func undefined in this context? - javascript

So I have a function like
func()
{
const curVal = this.curVal;
const callAgain = () => { func(); };
Axios.get('somecontroller/someaction')
.then(response =>
{
const newVal = response.data.curVal;
if(curVal === newVal)
setTimeout(callAgain, 500);
else
// ....
})
.catch(response =>
{
// ...
});
}
and my browser is complaining about the line
const callAgain = () => { func(); };
saying that func is undefined. Any idea why? How can I fix?

You cannot define a function the way you posted.
However, you can for example use the function keyword to define your function:
function func() {
...
}
func(); // it works!
Edit:
According to your comment, this is a object method declaration. In order to make this work, you first need to make sure your browser supports this particular ES2015 feature or if not, you transpile it to valid ES5.
Then you should be able to access the function using this.func():
const callAgain = () => { this.func(); };
In case you are using func() e.g. as a callback for a DOM event, you also have to make sure that this is bound correctly in func, for example by explicitly binding it in the constructor:
constructor() {
...
this.func = this.func.bind(this);
}

Define the function using either of the following:
function func(){ ... }
Or...
var func = function(){ ... }
When you define it like this:
func() { ... }
JavaScript thinks you're trying to execute an existing function called func, and then run the block of code { ... }

Related

JS Module : Difference between directly returning a function in an object and returning a function in an object returning a function

It's redundant but I am learning JS and I want to know how it really works.
Returning a function directly from a module
let func1 = function () {
let test = function () {
console.log("1");
}
return {
getTest : test
}
}
Returning a function by using a function
let func1 = function () {
let test = function () {
console.log("1");
}
return {
getTest : function () {
return test;
}
}
}
In the first case, the getTest property of your object points to a function, so calling it this way:
func1().getTest()
Should result in logging 1.
In the second case, getTest returns a function which returns another function, so you'd have to also call the result in order to get 1, this way:
func1().getTest()();
Calling just getTest will return your function object, rather than calling it.

How to execute (route) specific Javascript functions in one file, based on url, without a framework and interfering

Well, that's a loaded question (for search purposes), and potentially stupid.
I am experimenting for learning and I'd like to know what would be a great way to accomplish this.
All the reading I've done on routing addresses just the HTML part, and not the Javascript functionality once it loads.
Let's say this is the script file.
let path = window.location.pathname;
if (path.includes("/foo")) initFoo();
if (path.includes("/bar")) initBar();
function initFoo(){
console.log("foo");
function doSomething(){
console.log("something 1");
}
}
function initBar(){
console.log("bar");
function doSomething(){
console.log("something 2");
}
}
How can doSomething() be called from outside? The function is not available once the init() executes. If it's not inside an init(), then the functions collide.
Is this a classic OOP moment, where I'd have to create a Foo() and Bar() class where doSomething() is a method?
How would the code solving this look like?
Simple answer, it is not accessible, because it's in other scope.
More verbose answer, solution would be to use closure to get reference to this function. For example by returning doSomething inside of initBar
function initBar(){
console.log("bar");
function doSomething(){
console.log("something 2");
}
return doSomething;
}
So you can call it then as
const func = initBar();
func();
I think the answer to this depends on how you plan to apply this approach to whatever scale you're targeting for your Proof of Concept.
Having classes Foo() and Bar() each with their own doSomething() is definitely a feasible approach, but may not be necessary.
Are you able to assume that by visiting /foo that doSomething() will always be called?
Go with Dominik's answer:
let path = window.location.pathname;
if (path.includes("/foo")) initFoo()();
if (path.includes("/bar")) initBar()();
function initFoo(){
console.log("foo");
function doSomething(){
console.log("something 1");
}
return doSomething;
}
function initBar() {
console.log("bar");
function doSomething() {
console.log("something 2");
}
return doSomething;
}
Or might your init functions have more than one closure that you need to target?
In this case, you may be able to target your closures with a query parameter in your route:
/foo?method=doSomething
let path = window.location.pathname;
let params = Array.from(
(new URLSearchParams(window.location.search)).entries()
);
function getMethodFromParams(defaultMethod = 'doSomething') {
let [ method ] = params.find(function([ value, key ]) {
return key === 'method';
});
return method !== undefined && method.length > 0
? method
: defaultMethod;
}
let method = getMethodFromParams();
let action;
if (path.includes("/foo")) action = initFoo()[method];
if (path.includes("/bar")) action = initBar()[method];
action();
function initFoo() {
const method = getMethod();
console.log("foo");
function doSomething() {
console.log("something 1");
}
function doSomethingElse() {
console.log("something else 1");
}
return {
doSomething,
doSomethingElse
};
}
function initBar() {
const method = getMethod();
console.log("bar");
function doSomething() {
console.log("something 2");
}
function doSomethingElse() {
console.log("something else 2");
}
return {
doSomething,
doSomethingElse
};
}

Override JQuery functions (logging decorator)

I'd like to create a logging decorator around jQuery function but it is called only once (in initialization time). How to fix it? Please look at the code.
function makeLogging(f) {
function wrapper(...rest) {
console.log(f.name);
console.log(rest);
return f.apply(this, rest);
}
return wrapper;
}
const task = $('.task');
task.on = makeLogging(task.on);
task.on('click', () => console.log('hi'));
The click event does not display messages about the called function.
You are doing it a little bit wrong, if I caught the idea what you want to achieve. For functionality, you described, please try following:
task.on('click', makeLogging(() => console.log('hi')));
In your original code, you wrapped the functionality of on() function, but this on() function is not called as event handler - it only install actual event handler. That's why logging is called only once during installation of the handler.
Code example of answer
function makeLogging(f) {
function auxiliaryWrapper(x, rest) {
return () => {
console.log(f.name);
console.log(rest);
x();
}
}
function mainWrapper(...rest) {
const restWithWrap = rest.map(arg => {
if (typeof arg === 'function') {
return auxiliaryWrapper(arg,rest);
}
return arg;
});
console.log(restWithWrap);
return f.apply(this, restWithWrap);
}
return mainWrapper;
}
const task = $('.task');
task.on = makeLogging(task.on);
task.on('click', () => console.log('hi'));

How can I test for equality to a bound function when unit testing?

I want to test that an argument passed to a function is a function reference but the function reference is being passed using bind().
Consider this code which is to be tested (shortened for brevity):
initialize: function () {
this.register(this.handler.bind(this));
}
And this unit test to check if register() was called with handler():
it('register handler', function () {
spyOn(bar, 'register');
bar.initialize();
expect(bar.register.calls.argsFor(0)[0]).toEqual(bar.handler);
});
The arg doesn't equal the function reference I guess due to the bound function using bind() - how can I test that the correct function reference is being passed while still using the bind() method on it?
Note: This isn't specific to jasmine, I just thought it was appropriate because of the methods being used.
Instead of
expect(bar.register.calls.argsFor(0)[0]).toEqual(bar.handler);
you can do
expect(Object.create(bar.handler.prototype) instanceof bar.register.calls.argsFor(0)[0])
.toBe(true);
or
expect(Object.create(bar.handler.prototype)).
toEqual(jasmine.any(bar.register.calls.argsFor(0)[0]));
This works because the internal [[HasInstance]] method of the bound function delegates to the [[HasInstance]] method of the original function.
This blog post has a more detailed analysis of bound functions.
this.handler.bind(this) creates completely a new function, therefore it is not equal to bar.handler.
See Function.prototype.bind().
You can pass bounded function as argument to your initialize function and then test it, e.g.:
var handler = bar.handler.bind(bar);
bar.initialize(handler);
expect(bar.register.calls.argsFor(0)[0]).toEqual(handler);
I've managed to keep the test and code and work around it.
I spy on the function reference with an empty anon func, then call it when spying on the register method - if the spy gets called, I know it's passed the correct reference.
it('register handler', function () {
spyOn(bar, 'handler').and.callFake(function(){}); // do nothing
spyOn(bar, 'register').and.callFake(function(fn){
fn();
expect(bar.handler).toHaveBeenCalled();
});
bar.initialize();
});
I thought I'd add another approach that, to me, is a bit less awkward.
given a class like:
class Bar {
public initialize() {
this.register(this.handler.bind(this));
}
private register(callback) {}
private handler() {}
}
the full spec might look like:
describe('Bar', () => {
let bar;
beforeEach(() => {
bar = new Bar();
});
describe('initialize', () => {
let handlerContext;
beforeEach(() => {
bar.handler = function() {
handlerContext = this;
};
bar.register = jest.fn(callback => {
callback();
});
bar.initialize();
});
it('calls register with the handler', () => {
expect(bar.register).toHaveBeenCalledWith(expect.any(Function));
});
it('handler is context bound', () => {
expect(handlerContext).toEqual(bar);
});
});
});
In my case (using jest) I just mocked the implementation of bind for the function I wanted and I tweaked it so that it returns the original function and not a bound copy of it.
Specifically here's what I tried and worked:
Code to be tested:
// module test.js
export const funcsToExecute = [];
function foo(func) {
funcsToExecute.push(func);
}
export function bar(someArg) {
// bar body
}
export function run(someArg) {
foo(bar.bind(null, someArg));
}
I wanted to assert that when run is called, funcsToExecute contains bar
So I wrote the test like this:
import * as test from 'test';
it('should check that "funcsToExecute" contain only "bar"', () => {
jest.spyOn(test.bar, 'bind').mockImplementation((thisVal, ...args) => test.bar);
test.run(5);
expect(test.funcsToExecute.length).toBe(1);
expect(test.funcsToExecute[0]).toBe(test.bar);
});
For your example, I suppose it would be something like this:
it('register handler', function () {
spyOn(bar, 'register');
spyOn(bar.handler, 'bind').mockImplementation((thisVal, ...args) => bar.handler);
bar.initialize();
expect(bar.register.calls.argsFor(0)[0]).toBe(bar.handler);
});
though I haven't tested it.

jQuery plugin public method/function

I am trying to achieve something like the following but dont know whats wrong:
$.a = function() {
// some logic here
function abc(id) {
alert('test'+id);
}
}
$.a.abc('1');
I tried using the return function, but that doesnt seem to work either. Can someone please help.
Thank you for your time.
Since $.a must be a function in itself, you'll have to add the abc function as a property to the $.a function:
$.a = function () {
// some logic here...
};
$.a.abc = function (id) {
alert('test' + id);
};
If abc must be defined from within the $.a function, you can do the following. Do note that $.a.abc will not be available until $.a has been called when using this method! Nothing inside a function is evaluated until a function is called.
$.a = function () {
// Do some logic here...
// Add abc as a property to the currently calling function ($.a)
arguments.callee.abc = function (id) {
alert('test' + id);
};
};
$.a();
$.a.abc('1');
$.a = (function(){
var a = function() {
//...
};
a.abc = function() {
//...
}
return a;
})();

Categories

Resources