如何安全退出死循环的代码
抛出问题
在浏览器的终端输入代码:
while(true){}
毫无疑问,浏览器这个 Tab 立刻被卡死,当然平时写代码我们很少写这种无脑的死循环代码,但是有一种需求很能比较常见(比如避免灾难性正则回溯 等等),就是如果一段代码运行过久,为了避免它长期占用线程,耽误线程处理别的任务,我们希望能够设定一个时间,当线程跑这段代码的时候,超过设定的时间就不在执行这段代码了。
假设实现的这个函数叫 —— functionTimeout,我们大概可以这么使用。
const deadcycle = () => {
while(true){
console.log("I'm dead")
}
}
functionTimeout(() => deadcycle(), { timeout });
实现 functionTimeout
如何实现 functionTimeout 呢,可以利用 Node.js V8 虚拟机 来实现,提到虚拟机不要被吓着,事实上我们只需要两个 API new vm.Script(code[, options]) 和 script.runInNewContext。
演示一,注入上下文
vm.Script
函数的第一个参数是字符串(code),里面是待运行的代码,runInNewContext 第一个参数是 vm.Script 的执行上下文。
import vm from "node:vm";
// vm.Script 第一个参数是字符串,里面是待运行的代码
const script = new vm.Script('name = "CondorHero";');
const context = {};
// runInNewContext 第一个参数是 vm.Script 的执行上下文
script.runInNewContext(context);
// context = { name: 'CondorHero' }
console.log(context);
演示二,timeout
上面只是见识了 vm.Script
和 runInNewContext
的简单使用,不过要想实现 functionTimeout
我们还需要利用 runInNewContext
函数的第二个参数,它是一个对象,其中有一个属性为 timeout,它是一个整数,单位毫秒表示 vm.Script 执行 code 代码时,如果执行时间超过 timeout 就退出执行。
看个例子:
import vm from "node:vm";
// 增加一个死循环
const script = new vm.Script('while(true){};name = "CondorHero";');
const context = {};
const isTimeoutError = (error) => {
return error.code === "ERR_SCRIPT_EXECUTION_TIMEOUT";
};
try {
// 增加超时时间
script.runInNewContext(context, {
timeout: 500,
});
} catch (error) {
if (isTimeoutError(error)) {
console.warn("超时了");
}
}
程序将抛出错误:Error: Script execution timed out after 500ms
错误的 code 为: ERR_SCRIPT_EXECUTION_TIMEOUT
,但我们通过 trycatch 来吃掉了这个错误同时实现了程序超时功能。
大功告成
我们接下来简单基于,演示二的代码进行抽象即可实现 functionTimeout。
const functionTimeout = (func, timeout) => {
const script = new vm.Script("returnVal = _func()");
const context = {
_func: func,
};
const isTimeoutError = (error) => {
return error.code === "ERR_SCRIPT_EXECUTION_TIMEOUT";
};
try {
script.runInNewContext(context, { timeout });
} catch (error) {
if (isTimeoutError(error)) {
console.warn("超时了");
}
}
return context.returnVal;
};
简单调用验证下:
const deadcycle = () => {
while (true) {}
};
const result = functionTimeout(deadcycle, 500);
console.log(result);
输出结果:
➜ node index.js
超时了
undefined
如果是正常代码就会有结果:
const normalFunc = () => {
return "I'm normal";
};
const result = functionTimeout(normalFunc, 500);
console.log(result);
输出结果:
➜ node index.js
I'm normal
有一个库就是上面代码的实现:
function-timeout @sindresorhus。
上面演示的代码是 Node.js 中的 API,我们无法在浏览器中使用,那么怎么在浏览器中实现类似的逻辑呢。
不好意思,没有好的实现,vm-browserify 目前是 Node.js VM 模块比较好的实现,但是 vm-browserify 只是用 frame 技术来实现在浏览器中给待执行的代码 code 注入上下文而已,但对 timeout 则没有好的实现。
这实在是一大遗憾。