上下文保留评估
Context-preserving eval
我们正在构建一个小型 REPL,它会在用户输入 javascript 表达式时对其进行评估(使用 eval
)。由于整个事情都是事件驱动的,评估必须在一个单独的函数中进行,但是上下文(即所有声明的变量和函数)必须在调用之间保留。我提出了以下解决方案:
function* _EVAL(s) {
while (1) {
try {
s = yield eval(s)
} catch(err) {
s = yield err
}
}
}
let _eval = _EVAL()
_eval.next()
function evaluate(expr) {
let result = _eval.next(expr).value
if (result instanceof Error)
console.log(expr, 'ERROR:', result.message)
else
console.log(expr, '===>', result)
}
evaluate('var ten = 10')
evaluate('function cube(x) { return x ** 3 }')
evaluate('ten + cube(3)')
evaluate('console.log("SIDE EFFECT")')
evaluate('let twenty = 20')
evaluate('twenty + 40') // PROBLEM
如您所见,它适用于函数范围的变量(var
和 function
),但在块范围的变量(let
)上失败。
我如何编写一个上下文保留 eval
包装器,它也将保留块范围的变量?
代码在浏览器中运行,DOM并且 Workers 完全可用。
需要注意的是,所需的功能必须正确处理副作用,即每一行代码,或者至少每个副作用,都应该恰好执行一次。
链接:
| https://vane.life/2016/04/03/eval-locally-with-persistent-context/
如果用户输入的代码不打算在使用 evaluate
之外产生任何副作用,一种方法是将新输入字符串连接到旧输入字符串。所以,例如:
evaluate('ten + cube(3)')
evaluate('let twenty = 20')
结果如下 运行。第一次:
ten + cube(3)
第二次:
ten + cube(3)
let twenty = 20
这不是很优雅,因为代码每次都必须 运行 以前输入的所有代码,但它至少会使 repl 起作用。
function* _EVAL(codeToTry) {
let userCode = '';
while (1) {
while (!codeToTry) {
codeToTry = yield null;
}
try {
const newCode = userCode + ';' + codeToTry;
const result = eval(newCode)
// No error, so tack onto userCode:
userCode = newCode;
codeToTry = yield result;
} catch(err) {
// Error, don't tack onto userCode:
codeToTry = yield err
}
}
}
let _eval = _EVAL()
_eval.next()
function evaluate(expr) {
let result = _eval.next(expr).value
if (result instanceof Error)
console.log(expr, 'ERROR:', result.message)
else
console.log(expr, '===>', result)
}
evaluate('var ten = 10')
evaluate('function cube(x) { return x ** 3 }')
evaluate('ten + cube(3)')
evaluate('let twenty = 20')
evaluate('twenty + 40') // PROBLEM
如果您 运行 5; let a = 2
return 5
是本机 JavaScript 行为,这基本上就是您的最终陈述中发生的事情当您检查 twenty + 40
的值时编写程序。但是,一个快速的解决方法是收集两个结果,即完整结果 fullResult
和该步骤的结果 (stepResult
)。有了这两个,一旦您的 evaluate()
函数成功,我们就可以检查 stepResult
是否等于 undefined
,这在分配新变量值时发生。
如果是这种情况,我们将使用该值 undefined
。否则,我们使用 fullResult
的值,它适用于您问题中提供的代码的每种情况:
const pastEvals = [];
function* _EVAL(s) {
while (1) {
try {
s = yield eval(s)
} catch(err) {
s = yield err
}
}
}
let _eval = _EVAL()
_eval.next()
function evaluate(expr) {
pastEvals.push(expr)
const fullResult = _eval.next(pastEvals.join(';')).value
const stepResult = _eval.next(expr).value
if (fullResult instanceof Error)
console.log(expr, 'ERROR:', result.message)
else
console.log(expr, '===>', stepResult === undefined ? stepResult : fullResult);
}
evaluate('var ten = 10')
evaluate('function cube(x) { return x ** 3 }')
evaluate('ten + cube(3)')
evaluate('let twenty = 20')
evaluate('twenty + 40')
当您尝试使用更高级的 JS 函数(例如 async
/await
和 fetch
时,您会 运行 遇到这个问题,但它应该可以正常工作对于这些更简单的用例。如果您需要构建适用于更高级用途的东西,您可能需要在幕后创建一个虚拟 DOM,使用每个 运行 创建和销毁一个新的虚拟 DOM,以及等到任何创建的承诺在迭代之间得到履行和完成,因为这是任何 fetch
相关操作所需要的。
article you linked 包含一个实际有效的疯狂方法:在每个 eval()
调用期间,我们在该 eval
范围内创建一个新闭包并将其导出,以便我们可以使用它评估下一条语句。
var __EVAL = s => eval(`void (__EVAL = ${__EVAL.toString()}); ${s}`);
function evaluate(expr) {
try {
const result = __EVAL(expr);
console.log(expr, '===>', result)
} catch(err) {
console.log(expr, 'ERROR:', err.message)
}
}
evaluate('var ten = 10')
evaluate('function cube(x) { return x ** 3 }')
evaluate('ten + cube(3)')
evaluate('console.log("SIDE EFFECT")')
evaluate('let twenty = 20')
evaluate('twenty + 40') // NO PROBLEM :D
TL;DR
这是我在下面提出的推荐的最佳解决方案,支持所有表达式,包括基于承诺的表达式,如 fetch()
,利用 async
/await
和嵌套 evaluate()
在我的 fetch()
.
的最后 then()
Note (also mentioned in full post below)
The result of the nested evaluate()
expression is logged first. This is correct and to be expected as that nested expression runs within the fetch()
that runs it. Once the entire fetch runs, it will return undefined
just as a variable assignment would. For every other [non-recommended] solution in my answer below, the title
variable will be evaluated if and after the fetch()
statement has been fully evaluated successfully. This is because we are either forcefully deferring the evaluation of the title
variable by means of setTimeout()
or a pre-processed then()
, or by means of forced sequential loading in the "BONUS" solution at the bottom of this solution.
var __EVAL = s => eval(`void (__EVAL = ${__EVAL.toString()}); ${s}`);
async function evaluate(expr) {
try {
const result = await __EVAL(expr);
console.log(expr, '===>', result)
} catch (err) {
console.log(expr, 'ERROR:', err.message)
}
}
evaluate('var ten = 10')
evaluate('function cube(x) { return x ** 3 }')
evaluate('ten + cube(3)')
evaluate('let twenty = 20')
evaluate('twenty + 40')
evaluate('let title = ""')
evaluate('fetch("https://jsonplaceholder.typicode.com/todos/1").then(res => res.json()).then(obj => title = obj.title).then(() => evaluate("title"))')
疯狂解释
其他一些解决方案非常接近这里,所以我必须感谢 Bergi 和 Brandon McConnell——Bergi his/her 巧妙地使用闭包 eval()
和 Brandon 在使用方面的独创性“阶梯式”结果。
The correct solution does exist, and it does work with promises. For ease of use, I did use Bergi's solution as a foundation for my own, and if you do select my answer, I will gladly split the reputation bonus evenly with them.
只需将 evaluate()
函数设置为 async/await,您就可以让它与 promise 一起工作。从这里开始,您必须决定如何让它 运行— 有机地,其中 fetch()
声明 运行 像往常一样异步,或者同步地等待任何 Promise 被在进行下一个 evaluate()
调用之前解决。
在我的解决方案中,我选择了有机路线,因为这就是 JavaScript 实际上在本地工作的方式。在继续之前强制所有承诺 运行 将规避 JavaScript 的性质。例如,如果您使用此代码构建一个 JavaScript 引擎或编译器,您会希望该代码与您的引擎 运行 相同,就像它在网络上对其他用户的一样,因此有机会等待出发。
BONUS ✨✨
If you would like to explore the non-organic, forced-sequential ordering idea I mentioned above, please scroll down to the bottom of this solution where I explain that concept in detail, link to an external resource that explains how to forcefully execute promises sequentially, and show a live working prototype of that idea in action.
如果使用您的引擎的人想像在其他项目中一样等待 fetch()
to finish loading before proceeding, then they should adhere to the proper usage of then()
。
我们可以通过 fetch()
的几种方法之一来完成此操作:
- 在实际的
then()
声明中包含 evaluate()
有机和推荐
- 向我们的求值表达式添加一个链接命令,这将使我们能够 运行 一个又一个完成。这在纯执行中对我们来说效果很好,但它不是 不推荐 因为这会向实际评估逻辑添加特殊逻辑而不是被评估的代码,因此它更像是一个服务器-side 或 back-end evaluation 在某种意义上比实际的 JS 代码 运行ning.
- 使用
setTimeout()
在下一行添加延迟,为 fetch()
的完成提供时间。 不推荐 因为这不能保证承诺已经解决,无论是解决还是拒绝。 Fetch 和 async/await 都处理 promises,所以我们也应该使用 promises 来等待它们。
以下是所有三种方法的示例:
1。在我们的 fetch().then()
✅
中包含一个嵌套的 evaluate()
表达式
注意: 这里的一个重要注意事项是您将首先看到嵌套 evaluate()
表达式的结果。这是正确的,并且在 运行 的 fetch()
中的嵌套表达式 运行 是可以预期的。一旦整个获取 运行s,它将 return undefined
就像变量赋值一样。
对于我在下面的回答中的所有其他 [非推荐] 解决方案,title
变量将在 fetch()
语句被成功完全评估后进行评估。这是因为我们要么通过 setTimeout()
或预处理的 then()
强制推迟对 title
变量的评估,要么通过“奖励”解决方案中的强制顺序加载在此解决方案的底部。
var __EVAL = s => eval(`void (__EVAL = ${__EVAL.toString()}); ${s}`);
async function evaluate(expr) {
try {
const result = await __EVAL(expr);
console.log(expr, '===>', result)
} catch (err) {
console.log(expr, 'ERROR:', err.message)
}
}
evaluate('var ten = 10')
evaluate('function cube(x) { return x ** 3 }')
evaluate('ten + cube(3)')
evaluate('let twenty = 20')
evaluate('twenty + 40')
evaluate('let title = ""')
evaluate('fetch("https://jsonplaceholder.typicode.com/todos/1").then(res => res.json()).then(obj => title = obj.title).then(() => evaluate("title"))')
2。将 then()
链接到 evaluate()
(不推荐)⚠️
注意: 为了将这种链接方法添加到我们的 evaluate()
表达式中,我们必须 return 每次我们 运行 然后。然而,这些承诺 can/will 是自我解决的,因此它们只允许我们将 then()
语句链接到任何 evaluate()
调用的末尾。这是它的工作原理:
var __EVAL = s => eval(`void (__EVAL = ${__EVAL.toString()}); ${s}`);
async function evaluate(expr) {
try {
const result = await __EVAL(expr);
return new Promise(_ => _(console.log(expr, '===>', result)))
} catch (err) {
console.log(expr, 'ERROR:', err.message)
}
}
evaluate('var ten = 10')
evaluate('function cube(x) { return x ** 3 }')
evaluate('ten + cube(3)')
evaluate('let twenty = 20')
evaluate('twenty + 40')
evaluate('let title = ""')
evaluate('fetch("https://jsonplaceholder.typicode.com/todos/1").then(res => res.json()).then(obj => title = obj.title)')
.then(() => evaluate('title'))
3。使用 setTimeout()
(不推荐)⚠️
注意: 我之前提到的问题是,如果不等待承诺本身,就无法保证承诺何时结算。使用 setTimeout()
比选项 #2 更具优势,因为这个 运行s 作为纯 JS 并且不会通过 运行 在后台运行额外的进程来绕过 JS,但是这个解决方案需要你猜猜你的抓取可能需要多长时间才能完成。这是不推荐的,无论是对于这个 evaluate()
功能还是在实际项目中的实践。使用 fetch().then()
的选项 #1 是唯一提供等待承诺在输入的实际代码内结算并等待承诺成功结算的灵活性的解决方案。
❗ 即使有时当 运行 执行下面的代码片段时,在等待整整一秒后,fetch()
仍然没有完成,setTimeout()
会先执行生成一个空白字符串,而不是比所需的实际 title
字符串。经过反复测试,这似乎在大部分时间但不是所有时间都有效。
var __EVAL = s => eval(`void (__EVAL = ${__EVAL.toString()}); ${s}`);
async function evaluate(expr) {
try {
const result = await __EVAL(expr);
console.log(expr, '===>', result)
} catch (err) {
console.log(expr, 'ERROR:', err.message)
}
}
evaluate('var ten = 10')
evaluate('function cube(x) { return x ** 3 }')
evaluate('ten + cube(3)')
evaluate('let twenty = 20')
evaluate('twenty + 40')
evaluate('let title = ""')
evaluate('fetch("https://jsonplaceholder.typicode.com/todos/1").then(res => res.json()).then(obj => title = obj.title)')
evaluate('setTimeout(() => evaluate("title"), 1000)')
奖励:强制顺序执行(不推荐)⚠️
如果你确实想强制所有评估函数等待前一个函数完全完成后再继续,我强烈反对,因为这不是 JS 在实际浏览器中的工作方式,有一篇很棒的文章James Sinclair 关于 continuous/recursive promise chaining 的确切概念,您可以在标记为“A Sequential Solution”的部分下 read here。如果您选择走这条路,请务必不要简单地使用 Promise.all()
,因为这不能保证每个 promise 的执行顺序。相反,使用 promise().then()
.
的递归链
实际情况如下:
const exprs = [];
const evaladd = expr => exprs.push(expr);
var __EVAL = s => eval(`void (__EVAL = ${__EVAL.toString()}); ${s}`);
async function evaluate(expr) {
try {
const result = await __EVAL(expr);
return new Promise(_ => _(console.log(expr, '===>', result)))
} catch(err) {
console.log(expr, 'ERROR:', err.message)
}
}
function evaluateAll(exprs = []) {
evaluate(exprs[0]).then(() => exprs.length > 1 && evaluateAll(exprs.slice(1)));
}
evaladd('var ten = 10')
evaladd('function cube(x) { return x ** 3 }')
evaladd('ten + cube(3)')
evaladd('let twenty = 20')
evaladd('twenty + 40')
evaladd('let title = ""')
evaladd('fetch("https://jsonplaceholder.typicode.com/todos/1").then(res => res.json()).then(obj => title = obj.title)')
evaladd('title')
evaluateAll(exprs)
我们正在构建一个小型 REPL,它会在用户输入 javascript 表达式时对其进行评估(使用 eval
)。由于整个事情都是事件驱动的,评估必须在一个单独的函数中进行,但是上下文(即所有声明的变量和函数)必须在调用之间保留。我提出了以下解决方案:
function* _EVAL(s) {
while (1) {
try {
s = yield eval(s)
} catch(err) {
s = yield err
}
}
}
let _eval = _EVAL()
_eval.next()
function evaluate(expr) {
let result = _eval.next(expr).value
if (result instanceof Error)
console.log(expr, 'ERROR:', result.message)
else
console.log(expr, '===>', result)
}
evaluate('var ten = 10')
evaluate('function cube(x) { return x ** 3 }')
evaluate('ten + cube(3)')
evaluate('console.log("SIDE EFFECT")')
evaluate('let twenty = 20')
evaluate('twenty + 40') // PROBLEM
如您所见,它适用于函数范围的变量(var
和 function
),但在块范围的变量(let
)上失败。
我如何编写一个上下文保留 eval
包装器,它也将保留块范围的变量?
代码在浏览器中运行,DOM并且 Workers 完全可用。
需要注意的是,所需的功能必须正确处理副作用,即每一行代码,或者至少每个副作用,都应该恰好执行一次。
链接:
如果用户输入的代码不打算在使用 evaluate
之外产生任何副作用,一种方法是将新输入字符串连接到旧输入字符串。所以,例如:
evaluate('ten + cube(3)')
evaluate('let twenty = 20')
结果如下 运行。第一次:
ten + cube(3)
第二次:
ten + cube(3)
let twenty = 20
这不是很优雅,因为代码每次都必须 运行 以前输入的所有代码,但它至少会使 repl 起作用。
function* _EVAL(codeToTry) {
let userCode = '';
while (1) {
while (!codeToTry) {
codeToTry = yield null;
}
try {
const newCode = userCode + ';' + codeToTry;
const result = eval(newCode)
// No error, so tack onto userCode:
userCode = newCode;
codeToTry = yield result;
} catch(err) {
// Error, don't tack onto userCode:
codeToTry = yield err
}
}
}
let _eval = _EVAL()
_eval.next()
function evaluate(expr) {
let result = _eval.next(expr).value
if (result instanceof Error)
console.log(expr, 'ERROR:', result.message)
else
console.log(expr, '===>', result)
}
evaluate('var ten = 10')
evaluate('function cube(x) { return x ** 3 }')
evaluate('ten + cube(3)')
evaluate('let twenty = 20')
evaluate('twenty + 40') // PROBLEM
如果您 运行 5; let a = 2
return 5
是本机 JavaScript 行为,这基本上就是您的最终陈述中发生的事情当您检查 twenty + 40
的值时编写程序。但是,一个快速的解决方法是收集两个结果,即完整结果 fullResult
和该步骤的结果 (stepResult
)。有了这两个,一旦您的 evaluate()
函数成功,我们就可以检查 stepResult
是否等于 undefined
,这在分配新变量值时发生。
如果是这种情况,我们将使用该值 undefined
。否则,我们使用 fullResult
的值,它适用于您问题中提供的代码的每种情况:
const pastEvals = [];
function* _EVAL(s) {
while (1) {
try {
s = yield eval(s)
} catch(err) {
s = yield err
}
}
}
let _eval = _EVAL()
_eval.next()
function evaluate(expr) {
pastEvals.push(expr)
const fullResult = _eval.next(pastEvals.join(';')).value
const stepResult = _eval.next(expr).value
if (fullResult instanceof Error)
console.log(expr, 'ERROR:', result.message)
else
console.log(expr, '===>', stepResult === undefined ? stepResult : fullResult);
}
evaluate('var ten = 10')
evaluate('function cube(x) { return x ** 3 }')
evaluate('ten + cube(3)')
evaluate('let twenty = 20')
evaluate('twenty + 40')
当您尝试使用更高级的 JS 函数(例如 async
/await
和 fetch
时,您会 运行 遇到这个问题,但它应该可以正常工作对于这些更简单的用例。如果您需要构建适用于更高级用途的东西,您可能需要在幕后创建一个虚拟 DOM,使用每个 运行 创建和销毁一个新的虚拟 DOM,以及等到任何创建的承诺在迭代之间得到履行和完成,因为这是任何 fetch
相关操作所需要的。
article you linked 包含一个实际有效的疯狂方法:在每个 eval()
调用期间,我们在该 eval
范围内创建一个新闭包并将其导出,以便我们可以使用它评估下一条语句。
var __EVAL = s => eval(`void (__EVAL = ${__EVAL.toString()}); ${s}`);
function evaluate(expr) {
try {
const result = __EVAL(expr);
console.log(expr, '===>', result)
} catch(err) {
console.log(expr, 'ERROR:', err.message)
}
}
evaluate('var ten = 10')
evaluate('function cube(x) { return x ** 3 }')
evaluate('ten + cube(3)')
evaluate('console.log("SIDE EFFECT")')
evaluate('let twenty = 20')
evaluate('twenty + 40') // NO PROBLEM :D
TL;DR
这是我在下面提出的推荐的最佳解决方案,支持所有表达式,包括基于承诺的表达式,如 fetch()
,利用 async
/await
和嵌套 evaluate()
在我的 fetch()
.
then()
Note (also mentioned in full post below) |
---|
The result of the nested evaluate() expression is logged first. This is correct and to be expected as that nested expression runs within the fetch() that runs it. Once the entire fetch runs, it will return undefined just as a variable assignment would. For every other [non-recommended] solution in my answer below, the title variable will be evaluated if and after the fetch() statement has been fully evaluated successfully. This is because we are either forcefully deferring the evaluation of the title variable by means of setTimeout() or a pre-processed then() , or by means of forced sequential loading in the "BONUS" solution at the bottom of this solution. |
var __EVAL = s => eval(`void (__EVAL = ${__EVAL.toString()}); ${s}`);
async function evaluate(expr) {
try {
const result = await __EVAL(expr);
console.log(expr, '===>', result)
} catch (err) {
console.log(expr, 'ERROR:', err.message)
}
}
evaluate('var ten = 10')
evaluate('function cube(x) { return x ** 3 }')
evaluate('ten + cube(3)')
evaluate('let twenty = 20')
evaluate('twenty + 40')
evaluate('let title = ""')
evaluate('fetch("https://jsonplaceholder.typicode.com/todos/1").then(res => res.json()).then(obj => title = obj.title).then(() => evaluate("title"))')
疯狂解释
其他一些解决方案非常接近这里,所以我必须感谢 Bergi 和 Brandon McConnell——Bergi his/her 巧妙地使用闭包 eval()
和 Brandon 在使用方面的独创性“阶梯式”结果。
The correct solution does exist, and it does work with promises. For ease of use, I did use Bergi's solution as a foundation for my own, and if you do select my answer, I will gladly split the reputation bonus evenly with them.
只需将 evaluate()
函数设置为 async/await,您就可以让它与 promise 一起工作。从这里开始,您必须决定如何让它 运行— 有机地,其中 fetch()
声明 运行 像往常一样异步,或者同步地等待任何 Promise 被在进行下一个 evaluate()
调用之前解决。
在我的解决方案中,我选择了有机路线,因为这就是 JavaScript 实际上在本地工作的方式。在继续之前强制所有承诺 运行 将规避 JavaScript 的性质。例如,如果您使用此代码构建一个 JavaScript 引擎或编译器,您会希望该代码与您的引擎 运行 相同,就像它在网络上对其他用户的一样,因此有机会等待出发。
BONUS ✨✨
If you would like to explore the non-organic, forced-sequential ordering idea I mentioned above, please scroll down to the bottom of this solution where I explain that concept in detail, link to an external resource that explains how to forcefully execute promises sequentially, and show a live working prototype of that idea in action.
如果使用您的引擎的人想像在其他项目中一样等待 fetch()
to finish loading before proceeding, then they should adhere to the proper usage of then()
。
我们可以通过 fetch()
的几种方法之一来完成此操作:
- 在实际的
then()
声明中包含evaluate()
有机和推荐 - 向我们的求值表达式添加一个链接命令,这将使我们能够 运行 一个又一个完成。这在纯执行中对我们来说效果很好,但它不是 不推荐 因为这会向实际评估逻辑添加特殊逻辑而不是被评估的代码,因此它更像是一个服务器-side 或 back-end evaluation 在某种意义上比实际的 JS 代码 运行ning.
- 使用
setTimeout()
在下一行添加延迟,为fetch()
的完成提供时间。 不推荐 因为这不能保证承诺已经解决,无论是解决还是拒绝。 Fetch 和 async/await 都处理 promises,所以我们也应该使用 promises 来等待它们。
以下是所有三种方法的示例:
1。在我们的 fetch().then()
✅
中包含一个嵌套的 evaluate()
表达式
注意: 这里的一个重要注意事项是您将首先看到嵌套 evaluate()
表达式的结果。这是正确的,并且在 运行 的 fetch()
中的嵌套表达式 运行 是可以预期的。一旦整个获取 运行s,它将 return undefined
就像变量赋值一样。
对于我在下面的回答中的所有其他 [非推荐] 解决方案,title
变量将在 fetch()
语句被成功完全评估后进行评估。这是因为我们要么通过 setTimeout()
或预处理的 then()
强制推迟对 title
变量的评估,要么通过“奖励”解决方案中的强制顺序加载在此解决方案的底部。
var __EVAL = s => eval(`void (__EVAL = ${__EVAL.toString()}); ${s}`);
async function evaluate(expr) {
try {
const result = await __EVAL(expr);
console.log(expr, '===>', result)
} catch (err) {
console.log(expr, 'ERROR:', err.message)
}
}
evaluate('var ten = 10')
evaluate('function cube(x) { return x ** 3 }')
evaluate('ten + cube(3)')
evaluate('let twenty = 20')
evaluate('twenty + 40')
evaluate('let title = ""')
evaluate('fetch("https://jsonplaceholder.typicode.com/todos/1").then(res => res.json()).then(obj => title = obj.title).then(() => evaluate("title"))')
2。将 then()
链接到 evaluate()
(不推荐)⚠️
注意: 为了将这种链接方法添加到我们的 evaluate()
表达式中,我们必须 return 每次我们 运行 然后。然而,这些承诺 can/will 是自我解决的,因此它们只允许我们将 then()
语句链接到任何 evaluate()
调用的末尾。这是它的工作原理:
var __EVAL = s => eval(`void (__EVAL = ${__EVAL.toString()}); ${s}`);
async function evaluate(expr) {
try {
const result = await __EVAL(expr);
return new Promise(_ => _(console.log(expr, '===>', result)))
} catch (err) {
console.log(expr, 'ERROR:', err.message)
}
}
evaluate('var ten = 10')
evaluate('function cube(x) { return x ** 3 }')
evaluate('ten + cube(3)')
evaluate('let twenty = 20')
evaluate('twenty + 40')
evaluate('let title = ""')
evaluate('fetch("https://jsonplaceholder.typicode.com/todos/1").then(res => res.json()).then(obj => title = obj.title)')
.then(() => evaluate('title'))
3。使用 setTimeout()
(不推荐)⚠️
注意: 我之前提到的问题是,如果不等待承诺本身,就无法保证承诺何时结算。使用 setTimeout()
比选项 #2 更具优势,因为这个 运行s 作为纯 JS 并且不会通过 运行 在后台运行额外的进程来绕过 JS,但是这个解决方案需要你猜猜你的抓取可能需要多长时间才能完成。这是不推荐的,无论是对于这个 evaluate()
功能还是在实际项目中的实践。使用 fetch().then()
的选项 #1 是唯一提供等待承诺在输入的实际代码内结算并等待承诺成功结算的灵活性的解决方案。
❗ 即使有时当 运行 执行下面的代码片段时,在等待整整一秒后,fetch()
仍然没有完成,setTimeout()
会先执行生成一个空白字符串,而不是比所需的实际 title
字符串。经过反复测试,这似乎在大部分时间但不是所有时间都有效。
var __EVAL = s => eval(`void (__EVAL = ${__EVAL.toString()}); ${s}`);
async function evaluate(expr) {
try {
const result = await __EVAL(expr);
console.log(expr, '===>', result)
} catch (err) {
console.log(expr, 'ERROR:', err.message)
}
}
evaluate('var ten = 10')
evaluate('function cube(x) { return x ** 3 }')
evaluate('ten + cube(3)')
evaluate('let twenty = 20')
evaluate('twenty + 40')
evaluate('let title = ""')
evaluate('fetch("https://jsonplaceholder.typicode.com/todos/1").then(res => res.json()).then(obj => title = obj.title)')
evaluate('setTimeout(() => evaluate("title"), 1000)')
奖励:强制顺序执行(不推荐)⚠️
如果你确实想强制所有评估函数等待前一个函数完全完成后再继续,我强烈反对,因为这不是 JS 在实际浏览器中的工作方式,有一篇很棒的文章James Sinclair 关于 continuous/recursive promise chaining 的确切概念,您可以在标记为“A Sequential Solution”的部分下 read here。如果您选择走这条路,请务必不要简单地使用 Promise.all()
,因为这不能保证每个 promise 的执行顺序。相反,使用 promise().then()
.
实际情况如下:
const exprs = [];
const evaladd = expr => exprs.push(expr);
var __EVAL = s => eval(`void (__EVAL = ${__EVAL.toString()}); ${s}`);
async function evaluate(expr) {
try {
const result = await __EVAL(expr);
return new Promise(_ => _(console.log(expr, '===>', result)))
} catch(err) {
console.log(expr, 'ERROR:', err.message)
}
}
function evaluateAll(exprs = []) {
evaluate(exprs[0]).then(() => exprs.length > 1 && evaluateAll(exprs.slice(1)));
}
evaladd('var ten = 10')
evaladd('function cube(x) { return x ** 3 }')
evaladd('ten + cube(3)')
evaladd('let twenty = 20')
evaladd('twenty + 40')
evaladd('let title = ""')
evaladd('fetch("https://jsonplaceholder.typicode.com/todos/1").then(res => res.json()).then(obj => title = obj.title)')
evaladd('title')
evaluateAll(exprs)