You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Discord.js Bot interaction.reply未等待异步代码执行完成问题

问题根因

你遇到的问题不是async/await语法失效,是两个实际逻辑问题导致的异常表现:

  • Puppeteer的page.goto()方法默认仅等待页面load事件触发就返回,多数内容类站点的列表是在初始load事件后通过JS异步渲染的,你在goto执行完后立即抓取DOM,此时目标.round-number下的h3元素还没挂载到页面上,最终healthArray为空,取随机值时得到undefined,看起来就像回复逻辑提前执行。
  • Discord斜杠命令有3秒首次响应强制限制,Puppeteer冷启动完整Chromium实例+页面加载的耗时很容易超过这个阈值,一旦超时交互token会直接失效,后续再调用interaction.reply()会直接抛出交互已处理的错误,和异步等待逻辑无关。
  • 你写的Cheerio节点遍历逻辑本身是同步执行的,不存在漏等异步操作的问题。
修复方案

按以下步骤调整代码即可解决:

  1. 爬取逻辑启动前先调用interaction.deferReply()延迟响应,该方法会立即给Discord返回“请求已接收”的占位状态,将响应有效期延长至15分钟,彻底规避3秒超时问题。注意延迟响应后不能再调用reply(),需要用editReply()发送最终内容。
  2. 给page.goto()增加加载等待配置,同时主动等待目标选择器渲染完成后再抓取DOM,避免拿到未渲染完成的空页面。
  3. 增加异常捕获和兜底逻辑,避免Puppeteer运行报错时进程残留、或者爬取空内容时返回undefined。
  4. Puppeteer启动时加无沙箱参数,减少服务器环境下的启动耗时和权限报错。

修复后的完整可运行代码:

const { SlashCommandBuilder } = require('discord.js');
const puppeteer = require('puppeteer');
const cheerio = require('cheerio');

module.exports = {
    data: new SlashCommandBuilder()
        .setName("tips")
        .setDescription("获取随机健康常识"),
    async execute(interaction) {
        // 先延迟响应,规避Discord 3秒交互超时
        await interaction.deferReply();
        let browser;
        try {
            const healthArray = [];
            browser = await puppeteer.launch({
                args: ['--no-sandbox', '--disable-setuid-sandbox']
            });

            const page = await browser.newPage();
            // 等待网络空闲状态再判定页面加载完成,适配异步渲染内容
            await page.goto("https://www.example.com/health-facts/", {
                waitUntil: 'networkidle2',
                timeout: 15000
            });
            // 强制等待目标元素挂载,最多等5秒
            await page.waitForSelector('.round-number h3', { timeout: 5000 });

            const pageHtml = await page.evaluate(() => document.documentElement.innerHTML);
            const $ = cheerio.load(pageHtml);

            $(".round-number h3").each((i, el) => {
                const row = $(el).text()
                    .replace(/\s+/g, " ")
                    .replace(/^[0-9]+\.\s*/g, "")
                    .trim();
                healthArray.push(row);
            });

            await browser.close();
            browser = null;

            // 兜底处理,避免空数组报错
            const randomFact = healthArray.length
                ? healthArray[Math.floor(Math.random() * healthArray.length)]
                : "暂时无法获取健康常识,请稍后再试";
            
            await interaction.editReply(randomFact);
        } catch (err) {
            console.error("健康常识爬取失败:", err);
            if (browser) await browser.close();
            // 区分交互状态返回错误提示
            if (interaction.deferred) {
                await interaction.editReply("获取健康常识失败,请稍后重试");
            } else {
                await interaction.reply("获取健康常识失败,请稍后重试");
            }
        }
    },
};
优化建议
  • 不需要每次用户调用命令都启动一次Puppeteer爬取,可以加定时任务,比如每1小时爬取一次所有健康常识存入内存缓存,用户调用命令时直接从缓存取随机内容返回,响应速度可以达到毫秒级,也不会给目标站点造成频繁请求压力。
  • 如果部署环境资源有限,可以把Puppeteer换成更轻量的HTTP请求库(比如axios)直接拉取页面源码,只要站点内容不是强依赖客户端JS渲染,爬取耗时可以压缩到几百毫秒,完全不需要担心超时问题。

内容的提问来源于stack exchange,提问作者kaazue

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 07:45:29