1

How do I return the last value from the new Function() constructor? Seems eval() does this by default, as well as vm.Script#runInContext.

I just want to create a REPL in the browser (where I can do something like 2+2; and it returns 4 in the output) like eval, but without the drawbacks of eval (local function scope access, AsyncFunction, etc...)

Cobertos
  • 1,953
  • 24
  • 43
  • 1
    You’re looking for the value of a [completion record](//tc39.es/ecma262/#sec-completion-record-specification-type). Other than using `eval`, `new Function`, or something similar, these are not observable from within a JS script or module, as far as I’m aware. – Sebastian Simon Nov 12 '21 at 08:01
  • 1
    `eval` often gets a bad name, yes, it has security concerns, but in your case a repl the user is actioning the request, and its not been say proxied, as that would be bad. I would also say `new Function` would have the same issues here anyway. – Keith Nov 12 '21 at 08:18
  • @SebastianSimon hmm, yes, that is what I'm looking for. I don't mind using `new Function`, just looking to avoid `eval()`. It seems through that maybe `new Function` just doesn't support it? So I'll have to do something like `eval('(async function(){ ' + input + ' })()')`, because the REPL also needs to support asynchronous code w/ `await`s – Cobertos Nov 12 '21 at 08:31
  • After looking through the ECMAScript pseudo-code, it looks like this is just a limitation of Function objects. The entire section only mentions `Completion` once (so where's no real way to access completion records for functions). But this is something that `eval()` does return, as defined in the spec. So I guess eval is the only way – Cobertos Nov 12 '21 at 08:52

2 Answers2

1

You can use Web Worker to run something in isolated scope. There you can safely use eval without worrying about access to local scope variables of your page. Try something like this:

function run() {
  const code = `
  self.onmessage = async (evt) => {
    let result;
    try {
      result = await eval(evt.data);
    } catch(err) {
      result = 'Error: ' + err.message;
    }  
    self.postMessage(result);
  };
  `;
  const blob = new Blob([code], {
    type: "text/javascript"
  });
  const worker = new Worker(window.URL.createObjectURL(blob));
  worker.onmessage = function(e) {
    document.getElementById('out').value = e.data;
  }
  worker.postMessage(document.getElementById('repl').value);
}
<label for="repl">REPL:</label>
<input type="text" id="repl">
<button onclick="run()">Execute</button>
<p/>
<label for="out">Output:</label>
<input type="text" id="out" readonly>

Inspired by this answer :)

Vadim
  • 8,701
  • 4
  • 43
  • 50
0

There is no way to do this.

new Function() does not provide any way to access the "Completion Records" of it's invocation, according to the ECMAScript specification part on Function objects.

So if you need the last value, you have to use eval()

Cobertos
  • 1,953
  • 24
  • 43