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应该是在不同的线程里执行的,应该可以正常插入。

请大家帮忙释疑。 谢谢。


10 回复

你遇到的问题主要是由于 Node.js 的异步特性以及 MongoDB 操作的实际执行时机导致的。让我们详细分析一下你的代码,并提供一些改进的方法。

问题分析

  1. 异步操作与事件循环

    • 在 Node.js 中,所有 I/O 操作都是非阻塞的,这意味着它们不会阻塞主线程。然而,这些操作(如数据库写入)通常会在一个单独的线程或进程中执行,并且会在操作完成后触发回调。
  2. 循环中的异步操作

    • 你在循环中调用了 kitty.save 方法,这是一个异步操作。虽然 kitty.save 是异步的,但由于 JavaScript 的单线程特性,所有的 save 操作都会被推入事件队列中等待执行。因此,在循环结束之前,这些操作都不会被执行。
  3. 事件循环的执行顺序

    • 当循环结束后,事件循环才会开始处理队列中的异步操作。这就是为什么你会看到所有的日志输出都在循环之后才出现的原因。

示例代码及解释

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();

解释

  1. 递归调用

    • 我们将 kitty.save 放在一个递归函数 saveKitty 中,这样每次保存成功后,我们才会继续保存下一个 kitty。这样可以确保每个 save 操作都按顺序完成,而不会因为事件循环的机制导致所有操作同时进行。
  2. 计数器

    • 使用 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 函数,从而确保事件循环有足够的机会处理每个保存操作。

回到顶部