JavaScript开发Discord机器人时采集冷却功能的数据覆盖问题及解决方法
问题原因分析
你遇到的这个数据回退问题,本质是异步操作下的竞态条件(Race Condition),再加上同步文件写入的逻辑缺陷,具体拆解下:
- 旧数据快照的误用:当你触发砍树操作时,代码读取了当时的
user数据;3秒后触发采矿操作时,又读取了此时的user数据(这时候砍树还没完成,user还是初始状态)。两个异步任务各自拿着操作触发时的旧数据快照,后续的修改都是基于这个旧快照,而非实时最新的用户数据。 - 写入顺序的覆盖问题:砍树的冷却时间是5秒,你3秒后触发采矿,所以砍树会先完成并写入
wood+5的用户数据;但采矿完成时,它用的还是触发采矿时拿到的初始user(wood仍是初始值),写入时就把刚更新的wood又覆盖回了初始状态,只保留了stone+3的修改。 - 额外的语法错误:你的
saveUser对象里还有个明显的笔误,比如砍树代码里写了user.mine_time : user.mine_time,这是无效的对象键名,应该是wood_time: user.wood_time(采矿逻辑里对应mine_time: user.mine_time),这个问题虽不直接导致数据回退,但会导致时间属性无法正确保存。
修复方案
核心解决思路是每次修改用户数据前,都读取最新的文件内容,而非依赖操作触发时的旧数据快照,同时改用异步文件操作避免阻塞事件循环,下面是具体的修改步骤:
1. 替换同步文件操作为异步Promise版本
Node.js的fs.promises提供了异步的文件读写方法,既能避免同步操作阻塞事件循环,也能更好地和你的Promise链结合。
2. 在写入前重新读取最新用户数据
不要在操作开始时就保存user快照,而是在准备写入数据的环节,重新读取文件中的最新用户数据,基于这个最新数据做修改。
3. 优化冷却机制(可选但推荐)
当前的mine_cooltime和wood_cooltime是全局变量,若有多个用户同时操作会互相干扰,建议改成用户级的冷却(比如存在用户数据里,或用Map存储每个用户的冷却状态)。
修复后的代码示例
保留原sleep函数
function sleep(ms) { return new Promise((r) => setTimeout(r, ms)); }
采矿操作修复版
if (mine_cooltime === 0) { mine_cooltime = 1; const msgRef = await message.reply({embeds: [embed_mine_stone]}); sleep(user.mine_time) .then(() => msgRef.edit({embeds: [embed_mine_stone_end]})) // 重新读取最新的用户数据,确保修改基于实时状态 .then(async () => { const latestUser = JSON.parse(await fs.promises.readFile(filePath, 'utf8')); // 只修改需要更新的字段,其余保留最新值 return { ...latestUser, stone: latestUser.stone + 3, findStone: 1, mine_time: latestUser.mine_time }; }) .then(async (updatedUser) => { await fs.promises.writeFile(filePath, JSON.stringify(updatedUser)); }) .then(() => mine_cooltime = 0) .catch(err => { console.error('采矿操作出错:', err); mine_cooltime = 0; // 出错时重置冷却,避免用户永久无法操作 }); }
砍树操作修复版
if (wood_cooltime === 0) { wood_cooltime = 1; const msgRef = await message.reply({embeds: [embed_wood_cut]}); sleep(user.wood_time) .then(() => msgRef.edit({embeds: [embed_wood_cut_end]})) // 重新读取最新用户数据 .then(async () => { const latestUser = JSON.parse(await fs.promises.readFile(filePath, 'utf8')); return { ...latestUser, wood: latestUser.wood + 5, findWood: 1, wood_time: latestUser.wood_time }; }) .then(async (updatedUser) => { await fs.promises.writeFile(filePath, JSON.stringify(updatedUser)); }) .then(() => wood_cooltime = 0) .catch(err => { console.error('砍树操作出错:', err); wood_cooltime = 0; }); }
额外优化:多用户友好的冷却机制
如果你的机器人支持多用户,全局冷却变量会导致用户间互相干扰,建议改用Map存储每个用户的冷却状态:
// 全局定义冷却Map,键为用户ID+操作类型 const cooldowns = new Map(); // 采矿操作的冷却判断示例 const userId = message.author.id; const mineCooldownKey = `mine_${userId}`; if (!cooldowns.has(mineCooldownKey)) { cooldowns.set(mineCooldownKey, true); // ... 采矿操作逻辑 ... // 最后在重置冷却时删除对应键 cooldowns.delete(mineCooldownKey); } else { await message.reply('你正在采矿冷却中,请稍后再试!'); }
这样修改后,不管两个操作的执行顺序如何,每次写入数据都会基于文件中的最新内容,就不会出现一个操作覆盖另一个操作修改的情况了。
内容的提问来源于stack exchange,提问作者quail
相关产品推荐
相关产品推荐

