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

遍历Mongoose查询结果数组时出现对象未定义错误

问题描述

我写了下面这段Express路由代码:

router.get("/bookings", async (req, res) => {
    var bookings = await Booking.find()
    console.log(bookings)
    result = []
    for (i = 0; i < bookings.length; i++) {
        const challan = await Challan.findOne({ _id: bookings[i].challan });

        var packagesDelivered = 0;
        console.log('Booking', JSON.stringify(bookings[i]))
        console.log('GR NO', bookings[i].grNo)
        for (j = 0; j < bookings[i].deliveries.length; j++) {
            const delivery = await Delivery.findOne({ _id: bookings[i].deliveries[j] })
            packagesDelivered += delivery.noOfPackages;
        }
        

        result.push({ packagesDelivered: packagesDelivered, deliveries: bookings[i].deliveries, bookingDate: bookings[i].bookingDate, grNo: bookings[i].grNo, noOfPackages: bookings[i].noOfPackages, contents: bookings[i].contents, weight: bookings[i].weight, toPay: bookings[i].toPay, privateMark: bookings[i].privateMark, challanNo: challan.challanNo })

    }

    res.send(result)
})

运行时偶尔会在console.log('GR NO', bookings[i].grNo)处抛出错误,提示bookings[i]为undefined:

Booking undefined
        console.log('GR NO', bookings[i].grNo)
                                         ^

TypeError: Cannot read properties of undefined (reading 'grNo')
    at /Users/john/projects/myproj1/routes.js:396:36
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)

我搞不懂为什么会出现这个问题,毕竟循环条件是基于bookings.length设置的。以下是bookings的示例数据:

[
  {
    _id: new ObjectId("62d6a1c8663cadb009367207"),
    bookingDate: '2022-07-17',
    grNo: '379757',
    noOfPackages: 40,
    contents: 'OLD CLOTHX',
    weight: 2000,
    toPay: 12000,
    privateMark: 'UNKWN',
    deliveries: [],
    challan: new ObjectId("62d6a1c7663cadb0093671f7"),
    __v: 0
  },
  {
    _id: new ObjectId("62d6a1c8663cadb009367205"),
    bookingDate: '2022-07-16',
    grNo: '379753',
    noOfPackages: 6,
    contents: 'GOODS',
    weight: 180,
    toPay: 1260,
    privateMark: 'SHIVAM',
    deliveries: [],
    challan: new ObjectId("62d6a1c7663cadb0093671f7"),
    __v: 0
  },
  {
    _id: new ObjectId("62d6a1c8663cadb009367203"),
    bookingDate: '2022-07-14',
    grNo: '389553',
    noOfPackages: 1,
    contents: 'HARDWARE GOODS',
    weight: 50,
    toPay: 350,
    privateMark: 'UNKWN',
    deliveries: [],
    challan: new ObjectId("62d6a1c7663cadb0093671f7"),
    __v: 0
  },
  {
    _id: new ObjectId("62d6a1c7663cadb009367201"),
    bookingDate: '2022-07-16',
    grNo: '387197',
    noOfPackages: 11,
    contents: 'E RICKSHAW',
    weight: 450,
    toPay: 3150,
    privateMark: 'UNKWN',
    deliveries: [],
    challan: new ObjectId("62d6a1c7663cadb0093671f7"),
    __v: 0
  },
  {
    _id: new ObjectId("62d6a1c7663cadb0093671ff"),
    bookingDate: '2022-07-17',
    grNo: '379756',
    noOfPackages: 18,
    contents: 'OLD CLOTH',
    weight: 1140,
    toPay: 6840,
    privateMark: 'DL ',
    deliveries: [],
    challan: new ObjectId("62d6a1c7663cadb0093671f7"),
    __v: 0
  },
  {
    _id: new ObjectId("62d6a1c7663cadb0093671fd"),
    bookingDate: '2022-07-16',
    grNo: '379754',
    noOfPackages: 12,
    contents: 'OLD CLOTH',
    weight: 880,
    toPay: 5280,
    privateMark: 'BB',
    deliveries: [],
    challan: new ObjectId("62d6a1c7663cadb0093671f7"),
    __v: 0
  },
  {
    _id: new ObjectId("62d6a1c7663cadb0093671fb"),
    bookingDate: '2022-07-17',
    grNo: '79352',
    noOfPackages: 116,
    contents: 'WASTE PAPER',
    weight: 6920,
    toPay: 31140,
    privateMark: 'UNKWN',
    deliveries: [],
    challan: new ObjectId("62d6a1c7663cadb0093671f7"),
    __v: 0
  },
  {
    _id: new ObjectId("62d6a1c7663cadb0093671f9"),
    bookingDate: '2022-07-16',
    grNo: '86520',
    noOfPackages: 35,
    contents: 'BED SHEET',
    weight: 2800,
    toPay: 15900,
    privateMark: 'UNKWN',
    deliveries: [],
    challan: new ObjectId("62d6a1c7663cadb0093671f7"),
    __v: 0
  },
  {
    _id: new ObjectId("62d69f6e663cadb0093670de"),
    bookingDate: '2022-07-16',
    grNo: '388755',
    noOfPackages: 7,
    contents: 'HARDWARE GOODS',
    weight: 330,
    toPay: 2310,
    privateMark: 'UNKWN',
    deliveries: [],
    challan: new ObjectId("62d69f6a663cadb0093670b6"),
    __v: 0
  },
  {
    _id: new ObjectId("62d69f6d663cadb0093670dc"),
    bookingDate: '2022-07-15',
    grNo: '380048',
    noOfPackages: 6,
    contents: 'MOTOR PARTS',
    weight: 300,
    toPay: 2100,
    privateMark: 'UNKWN',
    deliveries: [],
    challan: new ObjectId("62d69f6a663cadb0093670b6"),
    __v: 0
  }
]
问题原因

核心问题是你在for循环里用了全局变量i和j,没有用let或const声明。因为路由是并发处理的,当第一个请求的循环遇到await(比如await Challan.findOne或await Delivery.findOne)时,JavaScript会暂停这个异步函数的执行,去处理其他请求。这时候其他请求的循环会修改全局的i值,当第一个请求恢复执行时,i已经超出了当前bookings数组的长度,导致bookings[i]变成undefined。

举个例子:假设第一个请求的bookings长度是10,循环到i=2时遇到await暂停;这时候第二个请求进来,它的循环把i加到了11;当第一个请求恢复执行,继续用i=11去访问bookings[11],自然就是undefined。

解决方案

把循环变量i和j用let声明,让它们变成块级作用域变量,每个循环迭代都有独立的变量副本,不会被其他请求干扰。同时,建议把result也用let声明,避免全局污染:

router.get("/bookings", async (req, res) => {
    const bookings = await Booking.find()
    console.log(bookings)
    let result = []
    // 用let声明i
    for (let i = 0; i < bookings.length; i++) {
        const challan = await Challan.findOne({ _id: bookings[i].challan });

        let packagesDelivered = 0;
        console.log('Booking', JSON.stringify(bookings[i]))
        console.log('GR NO', bookings[i].grNo)
        // 用let声明j
        for (let j = 0; j < bookings[i].deliveries.length; j++) {
            const delivery = await Delivery.findOne({ _id: bookings[i].deliveries[j] })
            packagesDelivered += delivery.noOfPackages;
        }
        

        result.push({ 
            packagesDelivered, 
            deliveries: bookings[i].deliveries, 
            bookingDate: bookings[i].bookingDate, 
            grNo: bookings[i].grNo, 
            noOfPackages: bookings[i].noOfPackages, 
            contents: bookings[i].contents, 
            weight: bookings[i].weight, 
            toPay: bookings[i].toPay, 
            privateMark: bookings[i].privateMark, 
            challanNo: challan.challanNo 
        })

    }

    res.send(result)
})
额外优化建议
  1. 避免嵌套循环+await的低效写法:当前代码里的嵌套await会导致请求处理速度很慢,因为每次都要等待数据库查询完成才能继续。可以用Promise.all批量处理查询,提升性能:
router.get("/bookings", async (req, res) => {
    const bookings = await Booking.find()
    // 批量查询所有challan
    const challanIds = bookings.map(b => b.challan)
    const challans = await Challan.find({ _id: { $in: challanIds } })
    // 把challan转成id映射,方便查找
    const challanMap = new Map(challans.map(c => [c._id.toString(), c.challanNo]))

    const result = await Promise.all(bookings.map(async booking => {
        // 批量查询当前booking的所有deliveries
        const deliveries = await Delivery.find({ _id: { $in: booking.deliveries } })
        const packagesDelivered = deliveries.reduce((sum, d) => sum + d.noOfPackages, 0)
        
        return {
            packagesDelivered,
            deliveries: booking.deliveries,
            bookingDate: booking.bookingDate,
            grNo: booking.grNo,
            noOfPackages: booking.noOfPackages,
            contents: booking.contents,
            weight: booking.weight,
            toPay: booking.toPay,
            privateMark: booking.privateMark,
            challanNo: challanMap.get(booking.challan.toString())
        }
    }))

    res.send(result)
})

这种写法把多次数据库查询合并成少数几次批量查询,性能会提升很多,尤其是当bookings数量较多时。

  1. 处理可能的null值:比如Challan.findOne或Delivery.findOne可能返回null,建议添加判断,避免后续访问属性时出错,比如:
const challan = await Challan.findOne({ _id: bookings[i].challan });
if (!challan) {
    // 可以跳过这条booking,或者给challanNo设默认值
    continue;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 19:18:37