You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用NodeVM的后端为何无法返回异步函数执行结果?

问题解决与编译器搭建建议

异步代码无响应的原因

你的后端代码里,await vm.run(code)只会等同步代码执行完就返回结果,但异步操作(比如setTimeout、Promise回调)是在同步代码执行结束后才触发的。这时候你已经把空的consoleOutput发给前端了,而后端控制台能看到输出是因为异步任务在响应发送后才跑完,触发了console.log。

修复代码

要捕获异步输出,得让VM等待所有异步任务完成,同时包装异步API来跟踪它们:

router.post('/execute', async (req, res) => {
  const code = req.body.code;
  let consoleOutput = '';
  // 存储所有需要等待的异步任务
  const asyncTasks = [];

  const vm = new NodeVM({
    console: 'redirect',
    sandbox: {
      console: {
        log: (...args) => {
          console.log(...args);
          consoleOutput += args.join(' ') + '\n';
        },
        error: (...args) => {
          console.error(...args);
          consoleOutput += 'ERROR: ' + args.join(' ') + '\n';
        }
      },
      // 包装setTimeout,将其执行转为Promise加入任务列表
      setTimeout: (callback, delay, ...args) => {
        const task = new Promise(resolve => {
          const timer = setTimeout(() => {
            callback(...args);
            resolve();
          }, delay);
          return timer;
        });
        asyncTasks.push(task);
      },
      // setInterval建议限制或禁止,避免无限运行占用资源
      setInterval: (callback, delay, ...args) => {
        const task = new Promise(resolve => {
          const timer = setInterval(() => {
            callback(...args);
            clearInterval(timer); // 仅执行一次,避免生成无限异步任务
            resolve();
          }, delay);
          return timer;
        });
        asyncTasks.push(task);
      },
      clearTimeout,
      clearInterval,
      JSON,
      Math,
      Date,
      RegExp,
      Array,
      Object,
      // 包装fetch,跟踪它的Promise
      fetch: (...args) => {
        const task = fetch(...args);
        asyncTasks.push(task);
        return task;
      },
      alert: message => {
        consoleOutput += 'ALERT: ' + message + '\n';
      }
    }
  });

  try {
    // 把用户代码包裹在async函数中,捕获顶层的Promise
    const userCodePromise = vm.run(`
      (async () => {
        ${code}
      })();
    `);
    // 如果用户代码返回Promise,也加入任务列表
    if (userCodePromise instanceof Promise) {
      asyncTasks.push(userCodePromise);
    }
    // 等待所有异步任务完成,用allSettled避免单个任务失败导致整体挂起
    await Promise.allSettled(asyncTasks);

    res.status(200).json({ consoleOutput });
  } catch (error) {
    res.status(500).json({ error: error.message });
    return;
  }
});

搭建简易纯JS编译器的实用建议

  • 安全第一:
    • 必须用vm2这类沙箱工具,绝对禁止用户代码访问Node核心模块(比如fs、child_process),防止恶意代码破坏服务器。
    • 给执行加超时限制,比如最多10秒,避免用户写死循环或无限异步任务拖垮服务器:
      // 添加超时逻辑
      const timeoutPromise = new Promise((_, reject) => {
        setTimeout(() => reject(new Error('执行超时')), 10000);
      });
      await Promise.race([Promise.allSettled(asyncTasks), timeoutPromise]);
      
  • 输出完善:
    • 除了console.log,还要捕获console.error、console.warn,同时处理未捕获的异常,避免服务器崩溃。
    • 模拟浏览器的alert、prompt,把这些输出转成控制台信息返回给前端。
  • 性能与资源控制:
    • 每次请求创建新的VM实例,避免沙箱被之前的代码污染。
    • 限制单个请求的内存使用,比如通过Node的--max-old-space-size参数配置,或在代码中监控内存占用。
  • 用户体验优化:
    • 前端添加加载动画,异步代码执行需要时间,让用户感知到后台正在处理。
    • 支持代码语法高亮、格式化,提升用户编写代码的体验。
    • 显示执行耗时,帮助用户了解代码的性能表现。
  • 功能扩展:
    • 支持ES6+语法,vm2默认支持,但如果需要兼容更低版本,可以用Babel预编译用户代码。
    • 添加常用代码示例库,比如Promise、fetch的测试用例,方便用户快速上手。
    • 支持多文件编译,若有需求,可允许用户上传多个JS文件并处理依赖关系。

内容的提问来源于stack exchange,提问作者Blaiz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 11:17:50