如何解决Discord SlashCommandBuilder无法异步加载MongoDB条目生成动态命令选项的问题?
解决Discord.js SlashCommandBuilder异步加载MongoDB数据作为选项的问题
你遇到的核心问题是命令加载流程是同步的,但数据库查询是异步的——当你的refreshCmds同步读取命令文件并把command.data转成JSON时,异步的数据库查询还没完成,所以选项根本没被添加到命令里。另外,原来的命令文件结构把data定义成了一个同步的Builder实例,没法容纳异步逻辑。
下面是完整的解决方案,我们会调整命令文件结构和命令加载流程来适配异步操作:
步骤1:修改命令文件结构,让data成为异步函数
把原来的data变量改成一个异步函数,这样我们可以在里面先完成数据库查询,再构建SlashCommandBuilder并添加选项。修改你的editfeature.js:
const { SlashCommandBuilder } = require("@discordjs/builders"); const { MessageEmbed, MessageActionRow, MessageButton } = require('discord.js'); const { ROLE } = require('../../../config/roles.js'); const config = require('../../../config/config.json') const channel = require('../../../config/channel_ids.json') const addfeatureSchema = require('../../database/models/addfeatureSchema.js'); // Set Role.<Role> if this command should be restricted to a specific role. Refer to /config/roles.js for the enums const allowedRoles = [ROLE.Owner]; // 把data改成异步函数,先查询数据库再构建命令 async function data() { // 异步获取所有数据库条目 const records = await addfeatureSchema.find(); // 初始化SlashCommandBuilder const commandBuilder = new SlashCommandBuilder() .setName("editfeature") .setDescription('Edit a feature embed') .addStringOption(option => { option.setName('id') .setDescription('ID') .setRequired(true); // 循环为每个数据库条目添加选项 for (const record of records) { option.addChoice(record.id, record.id); } return option; }); return commandBuilder; } async function run(interaction) { const client = interaction.client; // 这里保留你的命令执行逻辑 } module.exports = { allowedRoles, data, run };
步骤2:修改refreshCmds函数,支持异步加载命令
原来的refreshCmds是同步遍历文件,现在需要改成异步的,等待每个命令的data()函数执行完成,再获取构建好的Builder实例。修改你的index.js里的refreshCmds:
async function refreshCmds() { const commands = []; // 用异步的fs.promises.readdir替代同步方法,支持异步遍历 const groups = await fs.promises.readdir(`${config.command_dir}`); for (const group of groups) { const commandFiles = await fs.promises.readdir(`${config.command_dir}${group}`); const jsFiles = commandFiles.filter(file => file.endsWith('.js')); for (const file of jsFiles) { const command = require(`${config.command_dir}${group}/${file}`); // 等待异步的data()函数执行完成,拿到完整的命令Builder const commandBuilder = await command.data(); commands.push(commandBuilder.toJSON()); client_commands.set(commandBuilder.name, command); console.log(`Loaded command file: ${group} -> ${file}`); } } try { console.log('Started refreshing application (/) commands.'); await rest.put(Routes.applicationGuildCommands(client_id, guild_id), { body: commands }); console.log('Successfully reloaded application (/) commands.'); } catch (error) { console.error(error); } }
为什么原来的代码不生效?
- 异步执行时机问题:你原来在
addStringOption里调用getAThing().then(),这个回调是异步执行的——当return option的时候,循环还没开始,选项根本没被添加。等回调执行完,command.data已经被refreshCmds转成JSON并上传到Discord了,所以看不到选项。 - 同步加载流程:
refreshCmds用同步的fs.readdirSync遍历文件,直接读取command.data(一个同步创建的Builder),没有等待异步操作完成的机会。
额外注意事项
- 数据库查询错误处理:可以在
data()函数里添加try/catch,避免数据库查询失败导致命令加载中断:async function data() { try { const records = await addfeatureSchema.find(); // 构建命令... } catch (err) { console.error(`Failed to load records for editfeature command:`, err); // 返回一个降级版本的命令,或者抛出错误终止加载 return new SlashCommandBuilder() .setName("editfeature") .setDescription('Edit a feature embed (Failed to load options)'); } } - 命令更新时机:如果数据库里的条目发生变化,你需要重新调用
refreshCmds()来更新命令选项——可以在条目修改的逻辑里触发这个函数,或者定时刷新。
内容的提问来源于stack exchange,提问作者LegalEagle
相关产品推荐
相关产品推荐

