基础前端

如何安全退出死循环的代码

2022-08-11  本文已影响0人  CondorHero
如何安全退出死循环的代码.png

抛出问题

在浏览器的终端输入代码:

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.ScriptrunInNewContext 的简单使用,不过要想实现 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 则没有好的实现。

这实在是一大遗憾。

参考

上一篇下一篇

猜你喜欢

热点阅读