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

NodeJS for循环嵌套Promise异步赋值异常问题求助

问题:异步循环导致菜单分类的菜品列表为空怎么解决?

问题背景

我现在要实现一个接口,逻辑是先获取菜单分类列表,再遍历每个分类调用接口获取对应菜品,最终返回包含菜品的完整菜单数据。原本获取分类的逻辑是正常的,而且我用注释里的测试代码验证过单个分类调用菜品接口是可行的,但添加循环之后,返回结果里每个分类的itemsList始终是空的。

我的代码

'use strict';
var mongoose = require('mongoose'),
    MenuCategory = mongoose.model('MenuCategory');
module.exports = function(menuItemController) {
    var mod = {
        listEntireMenu(req, res) {
            return new Promise(function(resolve, reject) {
                var entireMenu = [];
                MenuCategory.find({}, function(err, menuCategories) {
                    if (err) {
                        return reject(err)
                    } else {
                        //---------------------------
                        // 验证调用其他控制器可行的测试代码
                        //---------------------------
                        // 
                        // var categoryWithItems = menuCategories[0].toObject();
                        // req.body.menuCategoryID = categoryWithItems._id;
                        // menuItemController.listAllMenuItemsByCategory(req, res).then((menuItems) => {
                        //     if(menuItems)
                        //     {
                        //         return resolve(menuItems);
                        //     }
                        //     else
                        //     {
                        //         return { success: false }
                        //     }
                        // });
                        //-----------------------------
                        for (var i = 0; i < menuCategories.length; i++) {
                            var categoryWithItems = menuCategories[i].toObject();
                            var subItems = [];
                            req.body.menuCategoryID = categoryWithItems._id;
                            menuItemController.listAllMenuItemsByCategory(req, res).then((menuItems) => {
                                if(menuItems)
                                {
                                    subItems = menuItems;
                                }
                                else
                                {
                                    return { success: false }
                                }
                            });
                            categoryWithItems.tester = { "itemsList" : subItems };
                            entireMenu.push(categoryWithItems);
                        }
                        return resolve(entireMenu)
                    }
                });
            }).then((menuCategories) => {
                if(menuCategories)
                {
                    return menuCategories
                }
                else
                {
                    return { success: false }
                }
            });
        },
    }
    return mod;
};

返回结果

[ 
    { 
        "_id": "5ed16fxxxxxxxx95676e37", 
        "locationID": "5ed16xxxxxxxx7295676e36", 
        "menuCategoryName": "Category One", 
        "Created_date": "2020-05-29T20:26:34.991Z", 
        "__v": 0, 
        "tester": { "itemsList": [] } 
    }, 
    { 
        "_id": "5ed170xxxxxx95676e38", 
        "locationID": "5ed16xxxxxxxx7295676e36", 
        "menuCategoryName": "Category Two", 
        "Created_date": "2020-05-29T20:26:48.799Z", 
        "__v": 0, 
        "tester": { "itemsList": [] } 
    } 
]

路由调用代码(route.js)

app.get('/api/listEntireMenu', (req, res) => {
    menuCategoryController.listEntireMenu(req, res).then(menuCategories => res.json(menuCategories));
})

问题分析与解决方案

没错,这绝对是异步操作的时序问题搞的鬼!你现在的代码逻辑里,menuItemController.listAllMenuItemsByCategory是异步的Promise调用,但你的循环完全没等这些异步请求返回结果,就直接把空的subItems塞进分类对象,然后把整个数组resolve出去了。等异步请求真的拿到菜品数据的时候,你的接口早就把空列表返回给前端了,自然看不到菜品。

而且你用var i还有变量作用域的坑——所有异步回调会共享同一个i变量,搞不好最后所有请求都用了最后一个分类的ID,不过核心问题还是没处理好异步等待。下面给你两个实用的解决方案:

方案一:用async/await(最简洁易读)

改成async函数,就能像写同步代码一样处理异步逻辑,可读性拉满:

'use strict';
var mongoose = require('mongoose'),
    MenuCategory = mongoose.model('MenuCategory');
module.exports = function(menuItemController) {
    var mod = {
        async listEntireMenu(req, res) {
            try {
                // 先获取分类列表,用exec()返回Promise更规范
                const menuCategories = await MenuCategory.find({}).exec();
                const entireMenu = [];

                // 用for...of循环逐个处理分类,await会等待每个请求完成
                for (const category of menuCategories) {
                    const categoryWithItems = category.toObject();
                    req.body.menuCategoryID = categoryWithItems._id;
                    // 等菜品接口返回结果再往下走
                    const menuItems = await menuItemController.listAllMenuItemsByCategory(req, res);
                    // 给分类加上菜品列表,空的话就设成[]
                    categoryWithItems.tester = { itemsList: menuItems || [] };
                    entireMenu.push(categoryWithItems);
                }

                return entireMenu;
            } catch (err) {
                // 捕获并抛出错误,让上层处理
                throw err;
            }
        },
    }
    return mod;
};

方案二:用Promise.all(并行处理,效率更高)

如果各个分类的菜品请求之间没有依赖,用Promise.all同时发起所有请求,比逐个等待快很多:

'use strict';
var mongoose = require('mongoose'),
    MenuCategory = mongoose.model('MenuCategory');
module.exports = function(menuItemController) {
    var mod = {
        listEntireMenu(req, res) {
            // 先获取分类列表
            return MenuCategory.find({}).exec()
                .then(menuCategories => {
                    // 把每个分类转换成一个获取菜品的Promise
                    const categoryPromises = menuCategories.map(category => {
                        const categoryWithItems = category.toObject();
                        req.body.menuCategoryID = categoryWithItems._id;
                        // 每个Promise完成后,给分类加上菜品列表再返回
                        return menuItemController.listAllMenuItemsByCategory(req, res)
                            .then(menuItems => {
                                categoryWithItems.tester = { itemsList: menuItems || [] };
                                return categoryWithItems;
                            });
                    });
                    // 等待所有Promise都完成,返回完整的菜单数组
                    return Promise.all(categoryPromises);
                })
                .catch(err => {
                    throw err;
                });
        },
    }
    return mod;
};

额外小贴士

  1. 把MenuCategory.find({}, callback)改成MenuCategory.find({}).exec(),这样返回的是Promise,和后面的异步逻辑更统一,也符合现代Node.js的最佳实践。
  2. 原来用var i的循环会有变量泄漏的问题,改成for...of或者let i就能避免,确保每个异步请求用的是当前循环的分类ID。
  3. 处理menuItems为空的情况时,直接赋值空数组,避免前端拿到undefined出问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 21:02:58