Nodejs 请教for循环中查询数据库时,只能得到最后一次查询结果?

Nodejs 请教for循环中查询数据库时,只能得到最后一次查询结果?

我express框架做blog的过程中遇到个很纠结的问题,我的mongo数据库里有两张表,

一个是存储用户信息的user表,字段是_id,email,name,password等…

一个是存储文章的blog表,字段是_id,title,content,author_id等…

blog表里的每篇文章除了标题内容等之外还有个命名为author_id的字段,存储文章作者id。

我现在想循环blog表,读取所有文章,找到每篇文章的author_id,

然后根据该author_id读取user表里的name字段,该字段表示作者名称。

但是在循环过程中发现读不到每个for循环时的序列值,只能拿到最后for结束后的序列值。

这是因为数据库查询是异步的,采用回调的方式处理。

这种情况下,我怎么拿到每次for循环的值呢?下面是代码:

module.exports = function(req, res) { var blog = require(’…/models/blog’); var user = require(’…/models/user’); var session_user = req.session.user; var condition = session_user? {author_id: session_user._id} : null;

blog.get(null, function(status, message) {
	for (var i=0, count=message.length; i<count; i++) {
		var label_arr = message[i].label.split(',');
		var j = 0;
		var num = label_arr.length;
		var new_arr = [];
	for (; j&lt;num; j++) {
		var k = 0;
		var label_arr2 = label_arr[j].split(',');
		var len = label_arr2.length;

		for (; k&lt;len; k++) {
			var clean_elem = label_arr2[k].replace(/(^[ \t\n\r]+)|([ \t\n\r]+$)/g,'');

			if (clean_elem != '') {
				new_arr.push(clean_elem);
			}
		}
	}

	message[i].label = new_arr;

	user.get({_id: message[i].author_id}, function(status, user_message) {
//这个回调函数只能被调用一次,根本拿不到对应用户的name值,我咧个去,纠结死了。。。
message[i].author_name = user_message[0].name; //这里出错,i的值永远是count

		blog.get(condition, function(status, self_message) {
			res.render('index', {
				title: 'NodeJs学习站',
				type: 'index',
				loginer: session_user,
				blog: message,
				self_blog: self_message
			});
		});
	});
}

});

};


11 回复

这个问题的核心在于 Node.js 的异步特性。在你的 for 循环中,每个数据库查询都是异步执行的,这意味着 for 循环不会等待查询完成就继续执行下一次迭代。因此,在回调函数中访问 i 时,循环可能已经结束了,导致你总是获取到最终的 i 值。

为了解决这个问题,可以使用闭包来保存当前循环中的 i 值。以下是一个示例代码:

module.exports = function(req, res) {
    var blog = require('../models/blog');
    var user = require('../models/user');
    var session_user = req.session.user;
    var condition = session_user ? { author_id: session_user._id } : null;

    blog.get(null, function(status, message) {
        var promises = [];

        for (var i = 0, count = message.length; i < count; i++) {
            (function(index) {
                var label_arr = message[index].label.split(',');
                var j = 0;
                var num = label_arr.length;
                var new_arr = [];

                for (; j < num; j++) {
                    var k = 0;
                    var label_arr2 = label_arr[j].split(',');
                    var len = label_arr2.length;

                    for (; k < len; k++) {
                        var clean_elem = label_arr2[k].replace(/(^[ \t\n\r]+)|([ \t\n\r]+$)/g, '');

                        if (clean_elem !== '') {
                            new_arr.push(clean_elem);
                        }
                    }
                }

                message[index].label = new_arr;

                // 使用 Promise 解决异步问题
                promises.push(new Promise((resolve, reject) => {
                    user.get({ _id: message[index].author_id }, function(status, user_message) {
                        if (status === 200 && user_message.length > 0) {
                            message[index].author_name = user_message[0].name;
                            resolve();
                        } else {
                            reject(new Error("User not found"));
                        }
                    });
                }));
            })(i);
        }

        // 等待所有 Promise 完成
        Promise.all(promises).then(() => {
            blog.get(condition, function(status, self_message) {
                res.render('index', {
                    title: 'NodeJs学习站',
                    type: 'index',
                    loginer: session_user,
                    blog: message,
                    self_blog: self_message
                });
            });
        }).catch(err => {
            console.error(err);
            res.status(500).send("Internal Server Error");
        });
    });
};

在这个解决方案中,我们使用了一个立即执行函数表达式(IIFE)来创建一个闭包,从而保存当前循环中的 i 值。这样,每个异步操作都能正确地访问到对应的 i 值。

此外,我们使用 Promise 来管理异步操作,通过 Promise.all 确保所有异步操作完成后才渲染页面。这样可以确保在渲染页面之前,所有的数据库查询都已经完成,并且能够正确地获取到每个文章的作者信息。


你这个javascript绑定的变量发生变化的问题 用下面这种方式解决,一定要用下面这个方式绑定一下变量。

function (i){ do(i); }(i变量绑定)

多看看javascript基础吧,js的坑还是很多的,除了这个,比较有名的还有this关键字,建议看看javascript高级程序设计。

你这种方法,我已经试过了,是不行的。

这个是典型的用同步的思路去写异步的程序,楼上的闭包可以解决问题,但是看上去比较怪异,难以理解。 最简单的用list的forEach来实现。 message.forEach(function(msg){ … … … user.get({_id: msg.author_id}, function(status, user_message) {}) … … … })

是的,这是更好的方式

你这种闭包写法也要分场合的,不是什么地方都能用啊

谢谢,我试下forEach。

我试了下,最后得到的还是只有一个结果,forEach跟for循环是一样的,附上user.get方法:

//查询用户信息 User.get = function(condition, callback) { if (condition && condition._id) { condition._id = new ObjectID(condition._id); }

User.base(function(collection) {
	collection.find(condition).toArray(function(err, user) {
		if (err) {
			return callback(0, err);
		}
	return callback(1, user);
});

});

};

这代码看的好累,你在异步循环里面调用 res.render?

是的,因为res.reder需要异步请求的数据。

这个问题主要是由于 JavaScript 的异步特性导致的。在 for 循环中执行异步操作(如数据库查询)时,循环变量 i 在异步回调执行时已经完成了所有迭代。因此,回调中的 i 始终为循环完成后的值。

为了确保在异步操作中能够获取到每次循环时正确的 i 值,可以使用 IIFE(立即执行函数表达式)来捕获当前的 i 值。以下是修改后的代码示例:

module.exports = function(req, res) {
    var blog = require('../models/blog');
    var user = require('../models/user');
    var session_user = req.session.user;
    var condition = session_user ? { author_id: session_user._id } : null;

    blog.get(null, function(status, message) {
        message.forEach(function(item) {
            (function(i) {
                user.get({ _id: item.author_id }, function(status, user_message) {
                    if (user_message && user_message.length > 0) {
                        item.author_name = user_message[0].name;
                    }

                    blog.get(condition, function(status, self_message) {
                        res.render('index', {
                            title: 'NodeJs学习站',
                            type: 'index',
                            loginer: session_user,
                            blog: message,
                            self_blog: self_message
                        });
                    });
                });
            })(message.indexOf(item));
        });
    });
};

在这个示例中,我们使用 forEach 方法遍历 message 数组,并通过 IIFE 将当前的 i 值传递给回调函数,从而确保在异步回调中可以访问到正确的索引值。这样,每个异步操作都可以正确地获取到相应的数据。

回到顶部