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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 05:35:23