Discord.js中收集反应用户时嵌套异步函数内数组返回为空的问题
问题核心原因
问题不是变量作用域导致的,本质是异步执行时序不匹配:
result变量定义在getUsersWhoReacted的函数作用域内,嵌套的回调函数可以通过闭包正常访问、修改这个变量,不存在作用域隔离的问题——如果真的是作用域问题,你在嵌套回调里操作result时会直接抛出引用错误,根本不可能成功在回调里打印出已经收集满用户ID的数组。- 问题的核心逻辑链条:
- Discord.js的
Collection.each()是同步遍历方法,本身不会等待传入的异步回调执行完成 - 你传给
each()的回调是async函数,当执行到await reaction.users.fetch()时,会直接挂起当前回调的执行,把控制权交还给外层的同步代码 - 外层代码不会等待所有挂起的异步回调跑完,会立刻继续执行到外层的
console.log(result),这时候所有用户抓取的网络请求还没返回,result数组还没有被推入任何数据,自然打印的是空数组 - 等后续所有异步请求完成、回调依次执行并往
result里push数据的时候,外层代码早就执行完了,所以你在回调里能看到完整的数组,但外层永远拿不到最终的填充结果。
- Discord.js的
可行解决方案
方案1:用Promise.all统一等待所有用户抓取任务完成
这是最贴合Discord.js Collection API的写法,把每个反应对应的抓取操作转成独立Promise,统一等待所有任务跑完再返回结果,附带去重逻辑避免同一个用户被多次统计:
const getUsersWhoReacted = async (reactionCollection) => { // 为每个反应生成一个独立的异步用户抓取任务 const userFetchTasks = reactionCollection.map(async (reaction) => { const reactedUsers = await reaction.users.fetch(); // 返回当前反应关联的用户ID列表 return reactedUsers.map(user => user.id); }); // 等待所有抓取任务完成 const allReactionUserArrays = await Promise.all(userFetchTasks); // 拍平二维数组、去重,得到最终的独立用户ID集合 const uniqueUserIds = [...new Set(allReactionUserArrays.flat())]; console.log(uniqueUserIds); // 可以正常把最终结果返回给调用方 return uniqueUserIds; }
方案2:沿用原有each遍历逻辑的适配写法
如果习惯使用each()的遍历风格,可以手动收集所有挂起的异步任务,等全部执行完成后再返回结果:
const getUsersWhoReacted = async (reactionCollection) => { let result = []; const pendingFetchTasks = []; reactionCollection.each((reaction) => { // 将每个反应的抓取逻辑封装成可追踪的Promise,存入任务队列 const fetchTask = (async () => { const users = await reaction.users.fetch(); users.forEach(user => result.push(user.id)); })(); pendingFetchTasks.push(fetchTask); }); // 阻塞等待所有异步抓取任务执行完成 await Promise.all(pendingFetchTasks); // 此时result已经完成所有数据填充,可以正常返回 const uniqueUserIds = [...new Set(result)]; console.log(uniqueUserIds); return uniqueUserIds; }
常见避坑提醒
不要直接在each()的参数里写async回调——这种写法相当于“发射即不管”的异步任务,each()遍历完不会等待这些任务执行,和你原来遇到的空数组问题完全一致。
内容的提问来源于stack exchange,提问作者Heinzen
相关产品推荐
相关产品推荐

