0

Closure values get lost in a function passed as a callback to another function defined by new Function() method.

Code

How can the function baz() be fixed to access the closure values in the callback?

Note: The function foo() cannot be modified.

const foo = () => {
  const b = 2;

  return (a) => {
    return a + b; // unable to access `b` here
  };
};

const bar = (a = 1, callback = foo()) => callback(a);

const baz = new Function(["a = 1", `callback = ${foo()}`], "return callback(a)");

console.log(bar(1)); // works fine and prints 3
console.log(baz(1)); // throws Uncaught ReferenceError: b is not defined
Vinay Sharma
  • 3,291
  • 4
  • 30
  • 66

2 Answers2

1

Don't use string interpolation with a function. What is happening can be seen here:

const foo = () => {
  const b = 2;
  return (a) => a+b;
};
console.log(new Function(["a = 1", `callback = ${foo()}`], "return callback(a)"))

The function (a) => a+b gets converted to a string, which is then put in the source code of the new function. Yes, this looses the closure - it looses the entire function object.

Instead, just write

const foo = () => {
  const b = 2;
  return (a) => a+b;
};
const bar = function(a = 1, callback = foo()) { return callback(a); };
const baz = new Function(["a = 1", "callback = foo()"], "return callback(a)");

console.log(bar(1));
console.log(baz(1));
console.log(bar);
console.log(baz);

As you can see from the logged functions, their code is now equivalent - and since everything in the demo snippet is global, they also work the same.

It's a bit different when foo (or anything else you want to access from the function code string) is defined in a local scope. In that case, you'll need to use a trick like this to explicitly make the value available to the dynamically generated code.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Well, it does seem to work here but not in my dev environment. The code doesn’t compile and throws `ReferenceError: foo is not defined at eval` – Vinay Sharma Jan 06 '22 at 21:07
  • Well, what is your dev environment? Is `foo` a global variable there? Why does it say `eval`, which the code doesn't use? – Bergi Jan 06 '22 at 21:13
  • My dev environment is a coding test platform. No, `foo` isn't a global variable there as the file has nothing except your solution. It says `eval` maybe because the compiler is not compatible with this syntax or it's trying to evaluate my code. Well, the platform runs some Mocha tests as well against some test cases, but they don't seem to throw the error as they don't even run as the code fails at compile-time error. If you feel that this error cannot be addressed under your solution, I can still accept your answer as it seems to work fine under a normal dev environment. – Vinay Sharma Jan 06 '22 at 22:37
  • If it's not global, the constructed `Function` can't see it - try the approach I linked, `const baz = new Function("{foo}", "return function(a = 1, callback = foo()) { return callback(a); };")({foo})`. But `ReferenceError` doesn't sound like a compile-time error, if you need further help with that platform please ask a new question. – Bergi Jan 06 '22 at 22:51
-1

I believe that Function constructors only execute within the global scope and do no create closures. This answer may help you:

Function Constructor MDN