猫鼬插入相同的数据三次,而不是迭代到下一个数据

时间:2019-01-22 18:06:43

标签: javascript node.js mongodb mongoose

我正在尝试将以下数据播种到我的MongoDB服务器中:

const userRole = {
    role: 'user',
    permissions: ['readPost', 'commentPost', 'votePost']
}
const authorRole = {
    role: 'author',
    permissions: ['readPost', 'createPost', 'editPostSelf', 'commentPost',
'votePost']
}
const adminRole = {
    role: 'admin',
    permissions: ['readPost', 'createPost', 'editPost', 'commentPost',
    'votePost', 'approvePost', 'approveAccount']
}
const data = [
    {
        model: 'roles',
        documents: [
            userRole, authorRole, adminRole
        ]
    }
]

当我尝试遍历此对象/数组并将此数据插入数据库时​​,最终得到的是“ adminRole”的三个副本,而不是三个单独的角色。我无法弄清楚为什么会发生,这使我感到非常愚蠢。

以下是我实际遍历对象并将其作为种子的代码,并且我知道它实际上正在获取每个值,因为我已经完成了console.log测试并且可以正确获取所有数据:

for (i in data) {
        m = data[i]
        const Model = mongoose.model(m.model)
        for (j in m.documents) {
            var obj = m.documents[j]

            Model.findOne({'role':obj.role}, (error, result) => {
                if (error) console.error('An error occurred.')
                else if (!result) {
                    Model.create(obj, (error) => {
                        if (error) console.error('Error seeding. ' + error)
                        console.log('Data has been seeded: ' + obj)
                    })
                }
            })
        }
    }

更新:

这是我阅读每个人的回复后想到的解决方案。两个私有函数生成Promise对象,用于检查数据是否存在以及插入数据,然后Promise.all满足所有Promise。

// Stores all promises to be resolved
var deletionPromises = []
var insertionPromises = []
// Fetch the model via its name string from mongoose
const Model = mongoose.model(data.model)
// For each object in the 'documents' field of the main object
data.documents.forEach((item) => {
    deletionPromises.push(promiseDeletion(Model, item))
    insertionPromises.push(promiseInsertion(Model, item))
})

console.log('Promises have been pushed.')
// We need to fulfil the deletion promises before the insertion promises.
Promise.all(deletionPromises).then(()=> {
    return Promise.all(insertionPromises).catch(()=>{})
}).catch(()=>{})

我不会同时包括promiseDeletionpromiseInsertion,因为它们在功能上是相同的。

const promiseDeletion = function (model, item) {
    console.log('Promise Deletion ' + item.role)
    return new Promise((resolve, reject) => {
        model.findOneAndDelete(item, (error) => {
            if (error) reject()
            else resolve()
        })
    })
}

更新2:您应该忽略我的最新更新。我已经稍微修改了发布的结果,但是即使那样,一半的时间角色也被删除而不插入。至于何时将角色实际插入服务器中,这是非常随机的。在这一点上,我感到非常困惑和沮丧。

2 个答案:

答案 0 :(得分:0)

使用Javascript时,您会遇到一个非常常见的问题:您不应该在常规的for(-in)循环中定义(异步)函数。发生的是,当您遍历这三个值时,将调用第一个异步查找。由于您的代码是异步的,因此nodejs在继续进行下一个循环迭代并计数到第三个值(此处为管理规则)之前,不会等待其完成。 现在,由于您在循环中定义了函数,因此当第一个异步调用结束时,for循环已经循环到了最后一个值,这就是为什么admin被插入三次的原因。

为避免这种情况,您可以将异步函数移出循环,以按值而不是引用强制调用。尽管如此,这还会带来很多其他问题,所以我建议您宁愿看一下Promise以及如何链接它们(例如,将所有猫鼬Promise放入一个数组中,然后使用Promise.all等待它们)或使用更加现代的async / await语法以及for-of循环,可轻松阅读并提供顺序的异步命令指令。

检查这个非常相似的问题:Calling an asynchronous function within a for loop in JavaScript

注意:for-of正在讨论性能方面的问题,因此请检查这是否适用于您的用例。

答案 1 :(得分:0)

在循环中使用异步函数可能会导致一些问题。

您应该更改使用findOne的方式以使其具有同步功能

首先,您需要将函数设置为异步,然后像这样使用findOne:

async function myFucntion() {
  let res = await Model.findOne({'role':obj.role}).exec();//Exec will fire the function and give back a promise which the await can handle.
  //do what you need to do here with the result..
}