Nodejs mongodb 异步io的问题
Nodejs mongodb 异步io的问题
刚开始学,很多原理的东西不是很了解,请大家帮帮忙。
如下代码:
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/test');
var counter = 1;
var Cat = mongoose.model('Cat', {
name: String
});
var kitty = new Cat({
name: ‘Zildjian’
});
for (i = 1; i < 1000; i++) {
console.log(“loop start”);
kitty.save(function(err) {
console.log("saved " + counter++);
if (err) // …
console.log(‘meow’);
});
console.log(“loop end”);
}
在我的预期中,循环在跑的过程中会有数据插入到数据库,但是事实并非如此。要等到循环结束,才开始有数据插入。我觉着即使循环是一个cpu密集操作,有可能会阻塞回调函数的执行,但是数据库io应该是在不同的线程里执行的,应该可以正常插入。
请大家帮忙释疑。 谢谢。
你遇到的问题主要是由于 Node.js 的异步特性以及 MongoDB 操作的实际执行时机导致的。让我们详细分析一下你的代码,并提供一些改进的方法。
问题分析
-
异步操作与事件循环:
- 在 Node.js 中,所有 I/O 操作都是非阻塞的,这意味着它们不会阻塞主线程。然而,这些操作(如数据库写入)通常会在一个单独的线程或进程中执行,并且会在操作完成后触发回调。
-
循环中的异步操作:
- 你在循环中调用了
kitty.save
方法,这是一个异步操作。虽然kitty.save
是异步的,但由于 JavaScript 的单线程特性,所有的save
操作都会被推入事件队列中等待执行。因此,在循环结束之前,这些操作都不会被执行。
- 你在循环中调用了
-
事件循环的执行顺序:
- 当循环结束后,事件循环才会开始处理队列中的异步操作。这就是为什么你会看到所有的日志输出都在循环之后才出现的原因。
示例代码及解释
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/test');
var counter = 1;
var Cat = mongoose.model('Cat', {
name: String
});
function saveKitty() {
var kitty = new Cat({
name: 'Zildjian' + counter
});
console.log("loop start");
kitty.save(function(err) {
console.log("saved " + counter++);
if (err) {
console.log('meow');
} else {
if (counter <= 1000) {
saveKitty(); // 递归调用保存下一个 kitty
}
}
});
console.log("loop end");
}
saveKitty();
解释
-
递归调用:
- 我们将
kitty.save
放在一个递归函数saveKitty
中,这样每次保存成功后,我们才会继续保存下一个kitty
。这样可以确保每个save
操作都按顺序完成,而不会因为事件循环的机制导致所有操作同时进行。
- 我们将
-
计数器:
- 使用
counter
来跟踪当前保存的是第几个kitty
,并在每次保存成功后增加它。当counter
达到 1000 时,停止递归调用。
- 使用
通过这种方式,你可以确保每个 kitty
都被正确地保存到数据库中,并且在保存操作之间有足够的间隔时间来避免过多的并发操作。
js 常见问题… 你for 循环有异步的话…得用闭包写法…
这个不太理解,能说的详细点,谢谢
改成这样就好了:
for(var i=0; i<10; i++) {
// or process.nextTick
setImmediate(function() {
console.log('loop start');
kitty.save(function(err) {
if(!err) console.log('saved!');
else console.log('save err~');
});
console.log('loop end!');
});
}
去了解下事件循环吧~
谢谢回复。那为什么如下代码不能按预期工作呢.我想知道到底是for循环执行完才save,还是在for循环运行中就异步的执行了save, 但是插入了console.log之后似乎没有按预期运行,谢谢
for (var i = 0; i < 10000; i++) {
// or process.nextTick
console.log('loop start');
setImmediate(function() {
kitty.save(function(err) {
if (!err) console.log('saved!');
else console.log('save err~');
});
});
console.log('loop end!');
}
因个人认为,用for不管怎么改都得等这个循环完成之后才能保存成功,因为那个save里面应该有一个process.nextTick
异步是当加载进程的时候,调用IO操作时,解释器会通知事件循环将事件和处理程序注入到事件队列中,等待下一个事件循环在调用,此时解释器会绕过异步操作直接执行下面的流程,一般情况下,下一个事件循环调用的时间是在当前作用域没有待执行的任务,才触发下一个事件循环
所以在循环100000次的时候,在循环中有个异步方法,这个异步方法会等待当前事件循环处理完任务后,在调用,所以看到的就是先console.log后save
学习到新姿势了…
感谢,讲的很详细
你的问题在于对 Node.js 的异步机制理解不完全。Node.js 是单线程的,它使用事件循环来处理异步操作。尽管 MongoDB 操作通常是异步的,但如果你的主事件循环被阻塞,这些异步操作可能不会按预期执行。
在这个例子中,for
循环本身是同步的,这意味着每次循环迭代都会立即执行 kitty.save()
方法,然后继续执行下一个迭代。由于事件循环没有机会处理这些异步操作,所以这些操作会被堆积起来,直到循环结束后才会依次执行。
你可以通过将 kitty.save()
放在一个异步函数中,并在每次保存后递归调用该函数来解决这个问题。这样可以确保每次保存后事件循环有时间处理保存操作。
以下是修改后的代码:
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/test');
var counter = 1;
var Cat = mongoose.model('Cat', { name: String });
function saveKitty(counter) {
if (counter > 1000) return;
var kitty = new Cat({ name: 'Zildjian' });
kitty.save(function(err) {
console.log("saved " + counter++);
if (err) console.log('meow');
saveKitty(counter);
});
}
console.log("loop start");
saveKitty(1);
console.log("loop end");
这样,每次保存操作完成后,都会递归调用 saveKitty
函数,从而确保事件循环有足够的机会处理每个保存操作。