MongoDB聚合后异步添加字段异常,求aggregate/addFields实现方案
问题:聚合房间数据时异步forEach导致字段未及时添加
我尝试聚合房间数据并为其添加新字段,但由于rooms是通过await获取的异步结果,forEach函数执行完成前就返回了rooms值,控制台输出顺序为:
a, rooms b, rooms (notification_count value)
使用的代码
let rooms = await this.aggregate([ { $match: { userIds: { $all: [userId] }, }, }, { $lookup: { from: "users", localField: "userIds", foreignField: "_id", as: "userProfiles", }, }, ]).sort({ updatedAt: -1 }); console.log("a", rooms); rooms.forEach(async (room) => { const notification_count = await ChatMessageModel.getUnreadMessagesCountByRoomId( room._id, userId, "is_read" ); room["notification_count"] = notification_count; console.log(notification_count); }); console.log("b", rooms);
更新
改用for循环替代forEach后符合工作流程需求,目前可以正常使用。但如果能直接在aggregate/addFields中实现该功能会更好,getUnreadMessagesCountByRoomId()仅返回整数。
解决方案
1. 为什么forEach会出问题?
forEach不支持异步函数,内部的await不会阻塞外部代码执行。所以console.log("b", rooms)会在所有异步的未读消息统计操作完成前执行,此时rooms还没添加上notification_count字段。
2. 用for...of循环替代forEach
这是最直接的修改,for...of支持异步等待,确保所有统计操作完成后再继续执行后续代码:
let rooms = await this.aggregate([ { $match: { userIds: { $all: [userId] } } }, { $lookup: { from: "users", localField: "userIds", foreignField: "_id", as: "userProfiles", }, }, ]).sort({ updatedAt: -1 }); console.log("a", rooms); // 改用for...of循环 for (const room of rooms) { const notification_count = await ChatMessageModel.getUnreadMessagesCountByRoomId( room._id, userId, "is_read" ); room.notification_count = notification_count; console.log(notification_count); } console.log("b", rooms); // 此时所有字段已添加完成
3. 直接在聚合管道中实现统计(推荐)
既然getUnreadMessagesCountByRoomId只是返回未读消息的整数数量,可以直接在MongoDB的聚合管道中完成统计,避免后续的异步循环操作,性能更优:
let rooms = await this.aggregate([ { $match: { userIds: { $all: [userId] }, }, }, { $lookup: { from: "users", localField: "userIds", foreignField: "_id", as: "userProfiles", }, }, // 关联消息集合,统计未读数量 { $lookup: { from: "chatMessages", // 替换为你的ChatMessage集合名称 let: { roomId: "$_id", currentUserId: userId }, pipeline: [ { $match: { $expr: { $and: [ { $eq: ["$roomId", "$$roomId"] }, // 匹配当前房间ID { $eq: ["$is_read", false] }, // 未读消息(根据你的字段定义调整) { $ne: ["$senderId", "$$currentUserId"] } // 排除当前用户发送的消息(按需调整) ] } } }, { $count: "count" } // 统计数量 ], as: "notificationCountData" } }, // 将统计结果转为数字,无数据则设为0 { $addFields: { notification_count: { $ifNull: [{ $arrayElemAt: ["$notificationCountData.count", 0] }, 0] } } }, // 移除临时的统计数据字段 { $unset: "notificationCountData" }, ]).sort({ updatedAt: -1 }); console.log(rooms); // 此时rooms已包含notification_count字段
这个方案直接在数据库层面完成所有数据处理,不需要在应用层做额外的异步循环,效率更高,也避免了异步时序问题。
内容的提问来源于stack exchange,提问作者Mr. Kenneth
相关产品推荐
相关产品推荐

