await只在异步函数中有效 - 在异步eval中

18

我想在async函数内部评估一些代码行。虽然以下代码是可以的,

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

以下代码会抛出错误: await只能在async函数中使用

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

我该如何处理这个问题?因为我的foo()函数是Puppetteer控制器函数,所以它应该是异步的。


2
退一步 - 你为什么想要eval代码?如果你告诉我们真正的问题,也许会有不同的解决方案 - VLAZ
我想根据不同的条件执行不同的操作。我的异步函数 foo() 很大,但是我只想通过 eval() 执行不同的小代码片段。 - Randy Vogel
1
你已经可以执行不同的函数而无需使用 eval - 例如 if,或调用完全不同的函数,多态性,设置具有功能的查找表等。 - VLAZ
将“what”和“where”作为回调传递? 实际上,我有一个很长的async()函数,而在其中间的某个地方,我需要执行不同的脚本,同时不丢失上下文。我决定通过eval()包含之前使用fs.readFileSync加载的脚本来处理它,如果有更好的处理方法,请给我一个示例。这是一个Puppetteer控制器函数,我需要动态选择测试数百个使用不同选择器的脚本。 - Randy Vogel
19
为什么每次有人问到eval时都会有人建议不要使用它?只需回答问题,因为它确实存在合法的使用场景。 - Vidar
显示剩余2条评论
5个回答

33

foo()并不一定需要是async,因为这对eval的执行上下文没有影响。相反,可能的解决方案是将ctxScript包装在一个自执行的异步函数中,像这样:eval("(async () => {" + ctxScript + "})()")


我的foo()应该是异步的,因为它是Puppetteer控制器函数。 - Randy Vogel
1
我的观点是:无论foo()是否是异步的,eval内部的代码是否在异步执行上下文中都没有影响。这就是我的答案。 - Ermir
所以,在eval()函数内部、async函数内部,await是无法工作的。 - Randy Vogel
从上面的评论来看,也许在这里使用eval不是解决您问题的最佳方案,因为它有巨大的缺点(速度较慢,执行上下文不同,存在代码注入的风险)。 - Ermir
谢谢,通常我不使用eval,但我很好奇为什么代码eval("(await fetch('https://wtfismyip.com/text', {'method': 'GET'})).text().then(res => {console.log(res)})")会显示错误"Uncaught SyntaxError: missing ) in parenthetical"。你的解决方案解决了这个问题。另一个解决方案是使用.then代替await - baptx
显示剩余2条评论

9

如果你想等待`eval`的执行,你可以使用以下代码:

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

这里使用了AsyncFunction构造函数。MDN有关于它的页面,其中描述了使用它和使用eval之间的区别:

注意:使用AsyncFunction构造函数创建的异步函数不会创建与其创建上下文的闭包;它们总是在全局作用域中创建。

运行它们时,它们只能访问自己的局部变量和全局变量,而不能访问AsyncFunction构造函数被调用的作用域中的变量。

这与使用带有异步函数表达式代码的eval不同。

这意味着如果您希望您的代码能够访问某些变量,您需要将它们添加到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"];

你在异步函数中运行示例吗?该示例创建了一个异步函数并使用await调用它,因此您需要在异步函数中使用它。 - Merlin04
这个答案对我来说更好,因为我可以轻松地运行原始代码而无需包装它,而globalThis允许我共享简单的上下文。 - Hasan Wajahat

6

最终采用了Ermir的答案:

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
在程序中睡觉通常不是一个好主意。如果您的评估脚本出现故障,它会一直睡眠,直到您重新启动系统。如果您确实想使用sleep,可以尝试将所有内容都包装在try catch中,这样如果代码出现问题,您的sleep就会停止。类似于 try {eval(ctxScript)} catch () {is_script_ended = true} - Frank
我知道这是四年前的事了,但为什么不等待eval呢?当前的解决方案会强制代码多花费一秒钟的时间,正如Frank所说,如果你的脚本抛出一个错误,脚本将永远等待。调用异步函数会返回一个promise,因此正确的解决方案是用await eval(ctxScript)替换foo()内的三行代码。这与Ermir的答案没有任何区别,只是指定了您需要在eval脚本中更改的内容。 - PoolloverNathan
我刚刚需要异步地评估不同的包含代码片段,并且需要紧急完成。此外,还有一个外部看门狗软件,通过超时控制和终止节点脚本,因此在这种情况下,永远等待是可以的。 - Randy Vogel

2
这里有另一种方法,无需休眠或执行任何复杂操作。
在传递给eval()的代码中,用另一个异步函数包装整个代码,并将其设置为某个变量,例如EVAL_ASYNC。然后在运行eval(ctxScript)之后,运行该异步函数await EVAL_ASYNC
let ctxScript = 'var EVAL_ASYNC = async function() {await foo1(); await foo2();}';
async function foo()
{
  eval( ctxScript );
  await EVAL_ASYNC();
}

1
它在浏览器中可以工作,但由于某些原因在node.js中无法工作,而在node.js中,可以使用以下代码: const func = eval(async () => {return ${yourCode}}); result = await func(); - fermmm

2

如果你想在一个较大的函数中动态调用一些异步代码,那么你可以提供一个回调函数来代替你执行这个操作。这样,你可以通过提供不同的回调函数来给你的函数添加不同的额外功能:

示例代码:

最初的回答:

// 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();
})


非常感谢您提供的出色示例!现在想象一下,在异步函数foo()的定义中,我有一些对象是在---某些代码---之后创建并在回调之前使用的,并且有成千上万个不同的双重等待(现在存储在文件中),当二次调用此函数foo()时,希望使用这些对象。 - Randy Vogel
这并没有解释如何从异步中获取表达式的值,只是稍后调用函数的方法。 - wow ow
@wowow,有什么问题吗?const result = await callback()将会得到值。我认为没有必要明确说明,因为你的操作方式不会改变。我重点是展示如何避免使用eval - VLAZ

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接