25

I want to eval() some lines of code inside of async function. While the following code is ok,

async function foo()
{
  await foo1();
  await foo2();
}

the following throws an error: await is only valid in async function

let ctxScript = 'await foo1(); await foo2();';
async function foo()
{
  eval( ctxScript );
}

How could I handle this? My foo() should be async as it is Puppetteer controller function

7
  • 2
    Take a step back - why do you want to eval the code? If you give us the real problem, perhaps it would turn out there is a different solution Commented May 17, 2019 at 13:13
  • I want to execute different actions due to different conditions. My async foo() is big, but only the small pieces of code that I want execute by eval() are different. Commented May 17, 2019 at 13:15
  • 1
    You can already execute different functions without needing eval - if for example, or calling a whole different function, polymorphism, setting up lookup tables with functionality and so on. Commented May 17, 2019 at 13:16
  • Ok, my X problem is that I want to use some different code inside of function, that should be async, what's the best Y to do it? Move all the async() code into includes, like said in answer #1? Commented May 17, 2019 at 13:20
  • 23
    Why do we always have to have someone saying to not use eval when asking about eval? Just answer the question, there are legitimate use cases for it. Commented Aug 1, 2019 at 9:41

5 Answers 5

38

foo() should not necessarily be async, as that has no effect on the execution context of eval. Instead, a possible solution is to wrap your ctxScript in a self-executing async function, like so: eval("(async () => {" + ctxScript + "})()")

Sign up to request clarification or add additional context in comments.

8 Comments

My foo() should be async as it is Puppetteer controller function.
My point was that whether foo() is async or not has no bearing whether the code inside the eval is in an async execution context or not. Hence my answer.
So it has, await is not working inside of eval(), inside of async function.
From the comments above, perhaps using eval here is not the best solution to your problem, as it has massive downsides (slower, different execution context, the risk for code injection).
I see, eval() there right now only as a quick patch, seeing and understanding and accepting all the risks. But this is my first time with node.js and async so I don't know better ways yet.
|
12

If you want to be able to await the eval you can use this:

await Object.getPrototypeOf(async function() {}).constructor("your code here")();

This uses the AsyncFunction constructor. MDN has a page on it which describes the differences between using it and using eval:

Note: async functions created with the AsyncFunction constructor do not create closures to their creation contexts; they are always created in the global scope.

When running them, they will only be able to access their own local variables and global ones, not the ones from the scope in which the AsyncFunction constructor was called.

This is different from using eval with code for an async function expression.

This means that if you have variables that you want your evaled code to be able to access, you can either

  1. Pass them in as arguments:
const testVar = "Hello world";
const result = await Object.getPrototypeOf(async function() {}).constructor('testVar', `
    console.log(testVar);
    await myAsyncFunc();
    return testVar;
`)(testVar);
// result will be "Hello world"
  1. Add them to globalThis:
const testVar = "Hello world";
globalThis["testVar"] = testVar;
const result = await Object.getPrototypeOf(async function() {}).constructor(`
    console.log(testVar);
    await myAsyncFunc();
    return testVar;
`)();
// result will be "Hello world"
delete globalThis["testVar"];

2 Comments

Are you running the example in an async function? The example creates an async function and calls it using await so you need to be in an async function to use it.
This answers works better for me as I can easily run the original code without wrapping it and the globalThis allows me to share simple context.
6

Ended up using Ermir`s answer:

let ctxScript = '(async () => {await foo1();await foo2();is_script_ended = true; })();';

async function foo()
{
  // a lot of code
  is_script_ended = false;
  eval( ctxScript );
  while(!is_script_ended){ await sleep(1000); }
  // a lot of code
}

3 Comments

Sleeping is never really a good idea in code hey. If your eval script ever fails, then it will sleep forever until you restart the system. If you really want to use sleep, maybe wrap everything in a try catch, so that if the code breaks, your sleep will stop. Something like try {eval(ctxScript)} catch () {is_script_ended = true}
I know that this was four years ago, but why not just await the eval? The current solution forces the code to take up to a second longer, and as Frank said, if your script throws an error, the script will wait forever. Calling an async function returns a promise, so the correct solution would be to replace the three lines inside foo() with await eval(ctxScript). This is not different from anything in Ermir's answer, which only specifies what you need to change in the eval script.
I just was in need to asynchronously eval different pieces of included code, and do it urgently. Also, there were an external watchdog software, that has controlled and terminated node scripts by timeout, so forever await was ok in that situation.
2

If you want to dynamically call some async code in some larger function, then you can supply a callback that does this for you. This way you can call your function with different extra functionality by giving it different callback functions to execute:

// some sample async functions
var resolveAfter2Seconds = function() {
  console.log("starting slow promise -> ");
  return new Promise(resolve => {
    setTimeout(function() {
      resolve("slow");
      console.log("<- slow promise is done");
    }, 2000);
  });
};

var resolveAfter1Second = function() {
  console.log("starting fast promise ->");
  return new Promise(resolve => {
    setTimeout(function() {
      resolve("fast");
      console.log("<- fast promise is done");
    }, 1000);
  });
};

//a function that accepts a callback and would await its execution
async function foo(callback) {
  console.log("-- some code --");
  await callback();
  console.log("-- some more code --");
}

//calling with an async function that combines any code you want to execute
foo(async () => { 
  await resolveAfter2Seconds();
  await resolveAfter1Second();
})

3 Comments

TY very much for the great example! Now imagine, that I have some objects in the definition of async function foo(), that created after ---some code--- and before callback, and have thousands of different double-awaits (now stored into files), when secondary calling this function foo(), that want to use these objects.
ths doesn't explain how to get an expression value from the async, only to call a function later
@wowow what's the problem? const result = await callback() will get the value. I didn't think it needs to be stated explicitly, since there is no change in how you'd do that. I was focusing on showing how to avoid eval.
2

Here is another way without having to sleep or do anything complex.

In the code you pass to eval(), wrap your entire code in another async function and set it to some variable, for example, EVAL_ASYNC. Then after running eval(ctxScript), run that async function await EVAL_ASYNC.

let ctxScript = 'var EVAL_ASYNC = async function() {await foo1(); await foo2();}';
async function foo()
{
  eval( ctxScript );
  await EVAL_ASYNC();
}

1 Comment

It works in the browser but not in node.js for some reason, in node.js this works instead: const func = eval(async () => {return ${yourCode}}); result = await func();

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.