Discord.js报错:Cannot set properties of null (setting 'staff')
Discord.js 运行时报错
TypeError: Cannot set properties of null (setting 'staff') 排查与修复 开发Discord机器人过程中,执行成员数据同步逻辑时触发运行时错误,错误触发位置为_timers.staff = newDevArray赋值行。
核心报错信息:
TypeError: Cannot set properties of null (setting 'staff')
原问题实现代码:
module.exports = async (client) => { client.guilds.cache.get('957290044291420220').members.fetch(); await dev.purgeall(); setTimeout(() => { client.guilds.cache.get('957290044291420220').members.cache.forEach(async x => { if(x.user.id != '926625186789326909' && x.user.id != '926625186789326909') { dev.set(x.user.id, { roles: x._roles, password: Math.floor(Math.random() * 9999999999) }); if(!dev.has('array')) dev.set('array', []); let support = await Support.findOne({ fetchStaff: x.user.id }); if(!support) { dev.push('array', x.user.id); } } }); setTimeout(async () => { let _timers = await Timers.findOne({ }); let savingDevs = await dev.get('array'); let newDevArray = []; savingDevs.forEach(async x => { let gettingDev = await dev.get(x); newDevArray.push({ userId: x, password: gettingDev.password, roles: gettingDev.roles }); }); _timers.staff = newDevArray; _timers.save(); }, 10000); }, 60000); }
根因排查
- 直接触发原因:
let _timers = await Timers.findOne({ });查询未返回匹配的数据库文档,_timers值为null,对null对象设置属性直接触发类型错误。 - 代码存在的其他时序隐患:
guild.members.fetch()是异步方法,原代码未加await,未等服务器成员列表拉取完成就执行后续逻辑,会导致成员缓存不全。- 两层
setTimeout靠固定时长等待异步操作完成的写法完全不可靠,数据库查询、接口请求的耗时受网络影响波动大,很容易出现等待时间到了但前置数据还没准备好的情况。 savingDevs.forEach(async x => { ... })中forEach不会等待内部异步回调执行完成,执行到赋值行时newDevArray大概率还没组装完成,即使不报错也会存入空数据。- 成员过滤条件重复,两个
x.user.id != '926625186789326909'判断完全一致,属于无效逻辑,如果是要排除两个特定用户需要修正第二个ID值。
修复方案
- 补全数据库查询空值兜底:如果Timers集合查不到对应文档,先创建默认空文档再进行属性赋值,从根源避免null赋值问题。
- 移除所有固定时长的
setTimeout等待,所有异步操作(成员拉取、数据库查询、缓存读写)全部使用await等待执行完成后再进入下一步逻辑,从根本上解决时序问题。 - 将异步遍历的
forEach替换为for...of循环,保证遍历过程中所有异步操作执行完成后,再执行后续的赋值、保存逻辑。 - 补全所有异步方法的
await标记,修正重复的判断条件。
修复后参考代码:
module.exports = async (client) => { const targetGuild = client.guilds.cache.get('957290044291420220'); // 等待服务器成员列表全量拉取完成 await targetGuild.members.fetch(); await dev.purgeall(); // 提前初始化存储数组 if (!dev.has('array')) dev.set('array', []); // 遍历成员做筛选 for (const member of targetGuild.members.cache.values()) { // 此处替换为你实际需要排除的用户ID,原代码两个ID重复属于无效判断 if (member.user.id === '926625186789326909') continue; dev.set(member.user.id, { roles: member._roles, password: Math.floor(Math.random() * 9999999999) }); const supportRecord = await Support.findOne({ fetchStaff: member.user.id }); if (!supportRecord) { dev.push('array', member.user.id); } } // 查询定时器配置,不存在则创建默认记录 let timersRecord = await Timers.findOne({}); if (!timersRecord) { timersRecord = await Timers.create({}); } const devIdList = await dev.get('array') || []; const staffDataList = []; // 等待所有成员数据查询完成再组装数组 for (const userId of devIdList) { const devData = await dev.get(userId); staffDataList.push({ userId: userId, password: devData.password, roles: devData.roles }); } timersRecord.staff = staffDataList; await timersRecord.save(); }
- 修复后可先手动在Timers集合插入一条测试文档验证逻辑,即使忘记提前插入,代码也会自动创建默认文档,不会再出现null赋值错误。
- 如果确实需要延迟1分钟执行该逻辑,可以把整个外层逻辑包一层
setTimeout,但内部不要嵌套固定等待,所有异步操作必须显式await结果。
内容的提问来源于stack exchange,提问作者Mr Chido 08
相关产品推荐
相关产品推荐

