使用node-cron调度Socket.io定时事件未按预期工作
问题诊断与修复方案
后端核心问题
1. Socket.io实例调用拼写错误
你在cron调度里写的gameRoutes.getIOSEasy.emit是拼写错误,对应导出的方法是getIOEasy,且需要调用方法(加括号)来获取实例,而非直接访问属性。
2. Cron表达式错误
你需要实现easy party每20秒触发一次,但当前表达式*/180 * * * * *是每180秒(3分钟)执行一次。node-cron的表达式格式为秒 分 时 日 月 周,每20秒的正确表达式是*/20 * * * * *。
修复后的后端cron代码
// cron schedule - easy party 每20秒触发重置事件 cron.schedule("*/20 * * * * *", () => { // 先获取Socket.io实例并判断是否存在,避免报错 const ioEasy = gameRoutes.getIOEasy(); if (ioEasy) { ioEasy.emit("reset", "Reset message from server"); console.log("触发easy party重置事件"); // 可选:添加日志验证定时任务是否执行 } });
前端核心问题
1. Reset事件参数未正确接收
前端代码中socket.on("reset", () => {})未接收服务器传来的message参数,但控制台打印时用到了该变量,会导致未定义报错,需补充参数接收。
2. Timer的闭包陷阱
用let timer在useEffect中保存定时器ID,组件重新渲染时会丢失引用,导致无法正确清除旧定时器。改用useRef保存定时器ID可避免此问题。
修复后的前端代码
import { useEffect, useState, useRef } from 'react'; // ... 其他组件代码 const timerRef = useRef(null); // 用useRef持久化定时器ID useEffect(() => { const socket = io(serverUrl, { path: path, auth: { crazygames_auth: token, }, }); // 初始化倒计时定时器 timerRef.current = setInterval(() => { setTimeLeft((prevTime) => { if (prevTime === 10) { socket.emit("sendResponses", buttonResponses); } return prevTime - 1; }); }, 1000); // 正确接收reset事件的消息参数 socket.on("reset", (message) => { console.log("Received reset event:", message); // 清除旧定时器 if (timerRef.current) { clearInterval(timerRef.current); } setTimeLeft(20); // 重新启动倒计时 timerRef.current = setInterval(() => { setTimeLeft((prevTime) => { if (prevTime === 10) { socket.emit("sendResponses", buttonResponses); } return prevTime - 1; }); }, 1000); }); socket.on("disconnect", () => { console.log("Disconnected from EasyParty"); }); return () => { socket.disconnect(); // 组件卸载时清除定时器 if (timerRef.current) { clearInterval(timerRef.current); } }; }, [cookies, buttonResponses]);
额外验证步骤
- 后端控制台添加日志,确认cron定时任务是否正常触发;
- 前端浏览器控制台检查是否有未定义变量、连接失败等报错;
- 后端
connection事件回调中添加日志,验证客户端是否成功连接Socket.io实例。
内容的提问来源于stack exchange,提问作者Baibhav Mandal
相关产品推荐
相关产品推荐

