javascriptfunctioncallbackclosuresuncaught-reference-error

JavaScript function closure value lost


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


Solution

  • 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.