Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a safe way to call `call` to call a function in JavaScript?

I want to call a function with a custom thisArg.

That seems trivial, I just have to call call:

func.call(thisArg, arg1, arg2, arg3);

But wait! func.call might not be Function.prototype.call.

So I thought about using

Function.prototype.call.call(func, thisArg, arg1, arg2, arg3);

But wait! Function.prototype.call.call might not be Function.prototype.call.

So, assuming Function.prototype.call is the native one, but considering arbitrary non-internal properties might have been added to it, does ECMAScript provide a safe way in to do the following?

func.[[Call]](thisArg, argumentsList)
like image 797
Oriol Avatar asked Jun 26 '15 15:06

Oriol


People also ask

Can we call function in function in JavaScript?

Approach: Write one function inside another function. Make a call to the inner function in the return statement of the outer function. Call it fun(a)(b) where a is parameter to outer and b is to the inner function.

What is the effective way to call the function?

Answer: B) call by reference. Explanation: In the call by reference, it will just copy the address of the variable to access it, so it will reduce the memory in accessing it.


1 Answers

That's the power (and risk) of duck typing: if typeof func.call === 'function', then you ought to treat it as if it were a normal, callable function. It's up to the provider of func to make sure their call property matches the public signature. I actually use this in a few place, since JS doesn't provide a way to overload the () operator and provide a classic functor.

If you really need to avoid using func.call, I would go with func() and require func to take thisArg as the first argument. Since func() doesn't delegate to call (i.e., f(g, h) doesn't desugar to f.call(t, g, h)) and you can use variables on the left side of parens, it will give you predictable results.

You could also cache a reference to Function.prototype.call when your library is loaded, in case it gets replaced later, and use that to invoke functions later. This is a pattern used by lodash/underscore to grab native array methods, but doesn't provide any actual guarantee you'll be getting the original native call method. It can get pretty close and isn't horribly ugly:

const call = Function.prototype.call;

export default function invokeFunctor(fn, thisArg, ...args) {
  return call.call(fn, thisArg, ...args);
}

// Later...
function func(a, b) {
  console.log(this, a, b);
}

invokeFunctor(func, {}, 1, 2);

This is a fundamental problem in any language with polymorphism. At some point, you have to trust the object or library to behave according to its contract. As with any other case, trust but verify:

if (typeof duck.call === 'function') {
  func.call(thisArg, ...args);
}

With type checking, you can do some error handling as well:

try {
  func.call(thisArg, ...args);
} catch (e) {
  if (e instanceof TypeError) { 
    // probably not actually a function
  } else {
    throw e;
  }
}

If you can sacrifice thisArg (or force it to be an actual argument), then you can type-check and invoke with parens:

if (func instanceof Function) {
  func(...args);
}
like image 64
ssube Avatar answered Oct 06 '22 11:10

ssube