如何在GraphQL控制器中使用fs.watch监听文件并正确返回测试结果
问题原因
fs.watch()的回调是异步执行的,回调内部的return仅作用于回调函数本身,不会作为外层postSolution方法的返回值- 原
postSolution方法注册完监听逻辑后没有等待回调执行就直接结束,异步方法无显式返回时默认返回null,所以你调用mutation会拿到null - 另外原代码中
tests.forEach(async () => {})的写法不会等待所有测试文件写入完成,后续逻辑执行时可能出现文件缺失的问题
修复方案
将监听逻辑封装为Promise,让postSolution等待测试结果返回后再响应,同时修复测试文件写入的异步等待问题,修改后的完整代码如下:
import { Context } from "../typescript/types"; import TestModel from "../models/TestModel"; const fs = require("fs-extra"); interface InputSolution { userId: string; exerciseId: string; testIDs: string[]; solution: string; } export default class SandboxController { async postSolution( parent: any, args: { input: InputSolution }, context: Context ) { // 1. 清空src、spec目录下的所有文件以及输出json文件 try { await fs.emptyDir("./userCode/src"); await fs.emptyDir("./userCode/spec"); console.log("目录清空成功"); } catch (err) { console.error(err); throw new Error("代码目录清空失败"); } // 2. 写入待测试函数和测试用例文件 const tests = await TestModel.find() .where("_id") .in(args.input.testIDs) .exec(); try { await fs.writeFile("./userCode/src/solution.js", args.input.solution); // 用Promise.all等待所有测试文件写入完成 await Promise.all(tests.map(async (test, index) => { await fs.writeFile( `./userCode/spec/test${index}.spec.js`, test.content ); })); console.log("测试文件写入成功"); } catch (err) { console.error(err); throw new Error("测试文件写入失败"); } // 3. 写入flag文件标记更新完成 try { await fs.writeFile( "./userCode/flag.txt", args.input.exerciseId + " - " + args.input.userId ); console.log("flag文件写入成功"); } catch (err) { console.error(err); throw new Error("flag文件写入失败"); } // 4. 监听flag文件变化,封装为Promise等待测试结果 return new Promise((resolve, reject) => { // 加超时逻辑,避免测试异常导致请求一直挂起 const timeout = setTimeout(() => { watcher.close(); reject(new Error("测试执行超时")); }, 30000); // 30秒超时可自行调整 const watcher = fs.watch("./userCode/flag.txt", async (eventType: string, filename: string) => { // 触发后立即关闭监听器,避免重复回调 watcher.close(); clearTimeout(timeout); console.log(`事件类型: ${eventType}`); try { const json = await fs.readFile( "./userCode/testLogs/output.json", "utf8" ); const output: any = json ? JSON.parse(json) : null; if (output && (output.numTotalTestSuites > 0 || output.numTotalTests > 0)) { console.log("测试套件总数", output.numTotalTestSuites); if (output.numFailedTestSuites === 0 && output.numFailedTests === 0) { console.log("所有测试通过"); resolve([{ testId: "12", passed: true }]); } else { console.log("测试失败,失败套件数", output.numFailedTestSuites); resolve([{ testId: "12", passed: false }]); } } else { resolve([{ testId: "12", passed: false }]); } } catch (err) { console.error("读取测试结果失败", err); resolve([{ testId: "12", passed: false }]); } }); }); } }
补充注意事项
- 新增的超时逻辑可以避免测试运行出错时请求长时间无响应,超时时间可根据你的测试运行时长自行调整
- 原代码中异常仅做了打印处理,修改后异常会直接抛出,GraphQL会自动返回错误信息给调用端,你也可以根据业务需求调整为返回自定义的错误格式
- 监听器触发后立即调用
close()关闭,避免重复触发回调导致的逻辑异常
内容的提问来源于stack exchange,提问作者alexB
相关产品推荐
相关产品推荐

