Node.js for循环阻塞/非阻塞问题:MongoDB批量插入异常解决
问题分析与解决方案
这个问题的核心是JavaScript异步操作和闭包的经典陷阱,我来一步步拆解并解决:
为什么会出现只插入第一条/打印"ok0"?
首先看你的代码里的几个关键问题:
- 循环变量的闭包引用问题:
db.json_db.count().exec()和model.save()都是异步操作,当它们的回调函数执行时,外层的for循环已经跑完了,此时i的值已经变成了json.length(也就是5)。加上你数组定义的语法错误(你写的json = { ["name":...], ... }是非法的,正确应该是数组json = [{...}, {...}, ...]),导致意外的变量引用问题。 - 未使用块级作用域变量:你循环里的
id、name没有用let/const声明,会变成全局变量,每次循环都会覆盖它们的值,异步回调执行时拿到的不是当前循环的变量值。 - 缺少错误处理:
exec的回调里没有处理err,如果数据库查询出错,你完全不知道问题出在哪。
修复方案
这里提供三种常用的修复方式,按推荐程度排序:
方案1:使用let声明循环变量(ES6+最简洁)
把for循环里的var i = 0改成let i = 0,let会在每次循环时创建一个新的变量绑定,异步回调会捕获当前循环的i值:
// 先修正数组定义的语法错误! const json = [ {"name":"Karl","id":"azo0"}, {"name":"Robert","id":"bdd10"}, {"name":"Joan","id":"difj90"}, {"name":"Hallyn","id":"fmak88"}, {"name":"Michael","id":"vma91"} ]; for(let i = 0; i < json.length; i++){ const { id, name } = json[i]; // 用解构+const避免全局变量 const ctx = {"id":id,"name":name}; db.json_db.count(ctx).exec(function(err, count) { // 先处理错误 if(err) { console.error("查询计数出错:", err); return; } if(count < 1){ // 直接用当前循环的id和name,不要再访问json[i]! const model = new User({ "name": name, "id": id }); model.save(function(err){ if(err) { console.error("保存数据出错:", err); return; } console.log("ok"+i); }); } }); }
方案2:使用forEach遍历数组
forEach的回调函数每次都会创建独立的作用域,天然避免闭包陷阱:
const json = [ {"name":"Karl","id":"azo0"}, {"name":"Robert","id":"bdd10"}, {"name":"Joan","id":"difj90"}, {"name":"Hallyn","id":"fmak88"}, {"name":"Michael","id":"vma91"} ]; json.forEach((user, index) => { const { id, name } = user; const ctx = {"id":id,"name":name}; db.json_db.count(ctx).exec((err, count) => { if(err) { console.error("查询计数出错:", err); return; } if(count < 1){ const model = new User({ name, id }); // 简写 model.save((err) => { if(err) { console.error("保存数据出错:", err); return; } console.log("ok"+index); }); } }); });
方案3:使用立即执行函数(IIFE,兼容ES5及以下)
如果你的环境不支持ES6,可以用IIFE把每次循环的用户数据封装到独立作用域里:
var json = [ {"name":"Karl","id":"azo0"}, {"name":"Robert","id":"bdd10"}, {"name":"Joan","id":"difj90"}, {"name":"Hallyn","id":"fmak88"}, {"name":"Michael","id":"vma91"} ]; for(var i = 0; i < json.length; i++){ // 用IIFE保存当前循环的user和i (function(user, index) { var id = user.id; var name = user.name; var ctx = {"id":id,"name":name}; db.json_db.count(ctx).exec(function(err, count) { if(err) { console.error("查询计数出错:", err); return; } if(count < 1){ var model = new User({ "name": name, "id": id }); model.save(function(err){ if(err) { console.error("保存数据出错:", err); return; } console.log("ok"+index); }); } }); })(json[i], i); }
额外优化建议
- 尽量使用
async/await来处理异步操作,代码会更易读,避免回调地狱:
const json = [/* 数据数组 */]; async function saveUsers() { for(const [index, user] of json.entries()) { const { id, name } = user; try { const count = await db.json_db.count({"id":id,"name":name}).exec(); if(count < 1) { const model = new User({ name, id }); await model.save(); console.log("ok"+index); } } catch(err) { console.error(`处理用户${name}出错:`, err); } } } saveUsers();
内容的提问来源于stack exchange,提问作者Richard
相关产品推荐
相关产品推荐

