Node.js MongoDB驱动程序异步/等待查询

54

我有一个使用mongodb原生驱动程序的node.js应用程序。在使用node v8.9.1迁移我的应用程序代码到async/await的过程中,我正在努力寻找一种优雅的方法来处理mongodb查询。mongodb驱动程序的主要问题是所有查询都使用回调函数,而异步方法必须使用promise函数。

替代方法:

  • mongoose- 建议使用promise查询已弃用,并且它强制使用架构模型,这对我的应用程序有点负担。
  • mongoist- 据说很好,因为它是针对async/await构建的,完全支持promise,但连接SSL到mongodb时出错且文档不足-让我放弃了这个解决方案。

我唯一成功实现的优雅方法是使用callback-promise npm包将mongodb驱动程序API转换为完全支持promise。

是否有其他高性能的优雅方式?


@MikaS 似乎需要“co”软件包。我基本上正在寻找一个完整的Promise本地库。 - Ido Lev
11个回答

70

由于所有答案都缺少一些细节 (如捕获块、检查客户端是否为null),我提出了自己的解决方案。测试使用Mongo服务器v4.0.7和Node JS驱动程序3.2.2。

请注意,示例是控制台程序,在finally块中关闭与服务器的连接。在Web应用程序中,连接将被重用。 请参见Node Mongo文档。此外,这些错误会被记录在诸如Winston或Morgan之类的库中,而不是日志记录在控制台上。

const MongoClient = require('mongodb').MongoClient;

const url = 'mongodb://localhost:27017';

async function findOne() {

    const client = await MongoClient.connect(url, { useNewUrlParser: true })
        .catch(err => { console.log(err); });

    if (!client) {
        return;
    }

    try {

        const db = client.db("testdb");

        let collection = db.collection('cars');

        let query = { name: 'Volkswagen' }

        let res = await collection.findOne(query);

        console.log(res);

    } catch (err) {

        console.log(err);
    } finally {

        client.close();
    }
}

await findOne();

有没有一种有效的方法可以在不关闭连接的情况下返回集合? - Iglesias Leonardo
我收到了 MongoRuntimeError: 连接池已关闭 的错误信息。 - Timo

46

编辑:'mongodb' v3.x

根据mongoDB ES6未来,您可以使用以下方式:

let MongoClient = require('mongodb').MongoClient;
const connectionString = 'mongodb://localhost:27017';

    (async () => {
        let client = await MongoClient.connect(connectionString,
            { useNewUrlParser: true });

        let db = client.db('dbName');
        try {
           const res = await db.collection("collectionName").updateOne({ 
               "someKey": someValue
           }, { $set: someObj }, { upsert: true });

           console.log(`res => ${JSON.stringify(res)}`);
        }
        finally {
            client.close();
        }
    })()
        .catch(err => console.error(err));

1
这太棒了,正是我想要的。谢谢! - Spike
自版本3开始,MongoClient返回一个客户端而不是一个db对象! - wiesson
7
这已经不再适用了。请问我们能否获取最新版本的MongoDB更新? - pguardiario
1
假设我们在使用await调用的函数内部调用了另外3个函数,那么我们需要使用await调用这3个内部函数吗? - Rishabh Dhiman

42

谢谢。使用ES6运行得很好:

const middleWare = require('middleWare');
const MONGO = require('mongodb').MongoClient;

router.get('/', middleWare(async (req, res, next) => {
    const db = await MONGO.connect(url);
    const MyCollection = db.collection('MyCollection');
    const result = await MyCollection.find(query).toArray();
    res.send(result);
}))

2
这是一个很棒的答案。对于其他试图弄清require('middleware')步骤的人,这里有一个很好的指南:https://medium.com/@Abazhenov/using-async-await-in-express-with-node-8-b8af872c0016 - JP Lew
这个也不再起作用了。有更新的可能吗? - pguardiario
我之前忽略了mongodb,现在使用mongoose。它是高度推荐的,因为有很多更新和支持promises。 - Ido Lev

18

这是我发现的与Mongo3和async/await兼容的最小代码片段。 享受吧!

module.exports = {
  myFunction: async (query) => {
    let db, client;
    try {
      client = await MongoClient.connect(process.env.MONGODB_CONNECTION_STRING, { useNewUrlParser: true });
      db = client.db(dbName);
      return await db.collection(collectionName).find(query).toArray();
    } finally {
      client.close();
    }
  }
}

此答案没有为其他答案增加任何独特的价值,且可执行的代码片段不起作用,其使用方法是错误的。 - Serhat Ates
2
是的,可能是因为我发现其他答案不够简明扼要。 - Sovattha Sok

11

如果您不传递回调函数,MongoDB客户端将返回一个Promise。

官方的MongoDB Node.js驱动程序提供了基于回调和基于Promise的与MongoDB交互方式,使应用程序能够充分利用ES6中的新功能。

来自官方文档


6
如果你想在不将数据加载到数组中的情况下使用光标进行操作,那么不能在find()或aggregate()函数中使用await,你需要使用以下代码: 由Usas更新: 通常情况下,使用toArray()的答案已经足够了。
但是当涉及到大量文档集合时,使用toArray()会超出可用的RAM。因此,在这些情况下,“高性能”的解决方案不能使用toArray()。
对于这些情况,可以使用MongoDB流,它运行良好,但甚至比使用流更简单的方法是:
const cursor = db.collection('name').aggregate(
    [
        {
            "$match": {code: 10}
        },
        {
          "$count": "count"
        }
    ],
    {
        "allowDiskUse": false
    }
)

for (let doc = await cursor.next(); doc != null; doc = await cursor.next()) {
    console.log('aggregate:', doc.count);
}

3
@Pax_Beach - 请编辑您的回答,以便我可以重新将其投票为有用。请查看上面的Usas评论。由于Usas解释了您的答案在罕见情况下的相关性,需要进行编辑(但不更改原意)。 - Ido Lev

4
我将把这个帖子作为答案发布,因为我不能在Ido Lev的回答下评论。当我达到50个声望时,我会将其移动。
不要忘记关闭数据库连接。否则,由于打开连接过多,您的应用程序可能无法连接到数据库(一个星期前发生了这种情况)。
您的查询可能成功或失败,所以在finally块中关闭连接是有意义的。
const db = await MongoClient.connect(url);
try {
    const stuff = await db.collection("Stuff").find({});
    // Do something with the result of the query
} finally {
    db.close();
}

更新: 看起来这也没有解决我的问题。有些人说你甚至不需要手动关闭连接。最好尽可能地在您的应用程序中重用连接。


1
是的!我正在尝试打开一个连接并重用它来处理当前请求所需的所有数据库操作。据我所知,打开连接是一项昂贵的操作,而读写要轻得多,并且可以通过单个连接完成。大多数示例都在同一代码块中连接、更新和关闭。我希望将我的打开连接存储在共享上下文中,以使其对所有函数都可用。 - Juan Lanus

4

基于Pax Beach的回答(已被downvote),我想添加一个评论,解释为什么在某些情况下Pat的回答是最好的。我没有足够的声望来添加评论。

对于一般情况,使用toArray()的答案就足够了。

但是当涉及到大量文档集合时,使用toArray()会超出可用RAM。因此,在这些情况下,“高性能”解决方案不得使用toArray()。

对于这些情况,您可以使用MongoDB流,它运行良好,但比使用流更简单的方法是:

const cursor = db.collection('someCollection').find({})
for (let doc = await cursor.next(); doc; doc = await cursor.next()) {
    // Process the document.
}

3

使用async/await的Mongoose查找查询

在使用async/await时不要使用mongoose.connect

    var router = require("express").Router()
    var mongoose = require("mongoose")
var await = require("await")
var async = require("async")

    var mongoUrl = "mongodb://localhost:27017/ekaushalnsdc"

    router.get("/async/await/find",async(req, res,  next) => {
      try {
        var db =  await mongoose.createConnection(mongoUrl)
          var colName = db.collection('collectionName')
        var result  = await colName.find({}).toArray()
        res.json(result)
    }catch(ex) {
       res.json(ex.message)
    }
    })

2

我来这里是为了 async/await 的解决方案,但是没有喜欢上任何一个答案,所以我想到了下面这段代码。

我正在使用本地驱动程序,这很有效。 我发现它比 for 循环更易读:

const cursor = await db.collection('myCollection').find({});

while (await cursor.hasNext()) {
  const doc = await cursor.next();
  // do whatever, ex.
  console.log(doc);
};
toArray()方法也可以,但是正如之前指出的那样,你必须确保不会获取一个大的结果集来占用你的内存。

在这里使用 await 并不必要,因为 hasNext()next() 是同步的,不会返回一个 Promise。因此你正在等待一个原始值 / 对象,它会被转换为已解决的 Promise(参见 MDN 文档)。 - JimmyBlu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接