How to fix MongoError: Cannot use a session that has ended

How to fix MongoError: Cannot use a session that has ended

我正在尝试使用 Node.js 从 MongoDB Atlas collection 中读取数据。当我尝试阅读我的 collection 的内容时,我收到错误 MongoError: Cannot use a session that has ended。这是我的代码

client.connect(err => {
    const collection = client
        .db("sample_airbnb")
        .collection("listingsAndReviews");

    const test = collection.find({}).toArray((err, result) => {
        if (err) throw err;
    });
    client.close();
});

我可以查询特定文档,但我不确定如何 return collection 的所有文档。我已经搜索过这个错误,我找不到太多关于它的信息。谢谢

在您的代码中,它不会等待 find() 完成执行,而是继续执行 client.close() 语句。因此,当它尝试从数据库中读取数据时,连接已经结束。我遇到了同样的问题并像这样解决了它:

// connect to your cluster
const client = await MongoClient.connect('yourMongoURL', { 
    useNewUrlParser: true, 
    useUnifiedTopology: true,
});
// specify the DB's name
const db = client.db('nameOfYourDB');
// execute find query
const items = await db.collection('items').find({}).toArray();
console.log(items);
// close connection
client.close();

编辑:整个事情应该在 async 函数中。

当我将 MongoClient 从 3.3.2 更新到最新版本(撰写本文时为 3.5.2)时,

运行 遇到了同样的问题。通过更改 [=15 仅安装 3.3.2 版本=] "mongodb": "3.3.2", 或者只使用异步和等待包装器。

如果问题仍然存在,请删除 node_modules 并重新安装。

一种选择是使用Promise链。 collection.find({}).toArray() 可以接收回调函数或 return 承诺,因此您可以使用 .then()

进行链式调用
collection.find({}).toArray() // returns the 1st promise
.then( items => {
    console.log('All items', items);
    return collection.find({ name: /^S/ }).toArray(); //return another promise
})
.then( items => {
    console.log("All items with field 'name' beginning with 'S'", items);
    client.close(); // Last promise in the chain closes the database
);

当然,这种菊花链使代码更加同步。当链中的下一个调用与前一个调用相关时,这很有用,例如在第一个中获取用户 ID,然后在下一个中查找用户详细信息。

应该并行(异步)执行几个不相关的查询,当所有结果返回时,处理数据库连接。 例如,您可以通过跟踪数组或计数器中的每个调用来做到这一点。

const totalQueries = 3;
let completedQueries = 0;

collection.find({}).toArray()
.then( items => {
    console.log('All items', items);
    dispose(); // Increments the counter and closes the connection if total reached
})

collection.find({ name: /^S/ }).toArray()
.then( items => {
    console.log("All items with field 'name' beginning with 'S'", items);
    dispose(); // Increments the counter and closes the connection if total reached
);

collection.find({ age: 55 }).toArray()
.then( items => {
    console.log("All items with field 'age' with value '55'", items);
    dispose(); // Increments the counter and closes the connection if total reached
);

function dispose(){
    if (++completedQueries >= totalQueries){
        client.close();
    }
}

您有 3 个查询。随着每个调用 dispose() ,计数器递增。当他们都调用 dispose() 时,最后一个也将关闭连接。

Async/Await 应该会更容易,因为它们解包了 then 函数的 Promise 结果。

async function test(){
    const allItems = await collection.find({}).toArray();
    const namesBeginningWithS = await collection.find({ name: /^S/ }).toArray();
    const fiftyFiveYearOlds = await collection.find({ age: 55 }).toArray();
    client.close();
}

test();

下面是一个示例,说明 Async/Await 如何通过等待一个异步函数完成后再调用下一个异步函数来最终使异步代码按顺序运行并 运行 效率低下,理想的情况是立即调用它们,只等到它们全部完成。

let counter = 0;

function doSomethingAsync(id, start) {
  return new Promise(resolve => {
    setTimeout(() => {
      counter++;
      const stop = new Date();    
      const runningTime = getSeconds(start, stop);
      resolve(`result${id} completed in ${runningTime} seconds`);
    }, 2000);
  });
}

function getSeconds(start, stop) {
  return (stop - start) / 1000;
}

async function test() {
  console.log('Awaiting 3 Async calls');
  console.log(`Counter before execution: ${counter}`);
  
  const start = new Date();
  
  let callStart = new Date();
  const result1 = await doSomethingAsync(1, callStart);
  
  callStart = new Date();
  const result2 = await doSomethingAsync(2, callStart);
  
  callStart = new Date();
  const result3 = await doSomethingAsync(3, callStart);
  
  const stop = new Date();

  console.log(result1, result2, result3);
  console.log(`Counter after all ran: ${counter}`);
  console.log(`Total time to run: ${getSeconds(start, stop)}`);
 }

test();

注意:如上例中的等待使调用再次按顺序进行。如果每个 运行 需要 2 秒,函数将需要 6 秒才能完成。

集所有优点于一身,您会希望在 运行 立即调用所有呼叫的同时使用 Async/Await。幸运的是,Promise有一个方法可以做到这一点,所以test()可以这样写:-

async function test(){
    let [allItems, namesBeginningWithS, fiftyFiveYearOlds] = await Promise.all([
        collection.find({}).toArray(),
        collection.find({ name: /^S/ }).toArray(),
        collection.find({ age: 55 }).toArray()
    ]);

    client.close();
}

这是一个演示性能差异的工作示例:-

let counter = 0;

function doSomethingAsync(id, start) {
  return new Promise(resolve => {
    setTimeout(() => {
      counter++;
      const stop = new Date();    
      const runningTime = getSeconds(start, stop);
      resolve(`result${id} completed in ${runningTime} seconds`);
    }, 2000);
  });
}

function getSeconds(start, stop) {
  return (stop - start) / 1000;
}

async function test() {
  console.log('Awaiting 3 Async calls');
  console.log(`Counter before execution: ${counter}`);
  const start = new Date();
  
  const [result1, result2, result3] = await Promise.all([
        doSomethingAsync(1, new Date()),
        doSomethingAsync(2, new Date()),
        doSomethingAsync(3, new Date())
  ]);
  
  const stop = new Date();

  console.log(result1, result2, result3);
  console.log(`Counter after all ran: ${counter}`);
  console.log(`Total time to run: ${getSeconds(start, stop)}`);
 }

test();

其他人已经谈到了这一点,但我只想强调 .toArray() 是异步执行的,因此您需要确保它在关闭会话之前已完成

这行不通

const randomUser = await db.collection('user').aggregate([ { $sample: { size: 1 } } ]);
console.log(randomUser.toArray()); 
await client.close();

这将

const randomUser = await db.collection('user').aggregate([ { $sample: { size: 1 } } ]).toArray();
console.log(randomUser); 
await client.close();
client.connect(err => {
    const collection = client
        .db("sample_airbnb")
        .collection("listingsAndReviews");

    const test = collection.find({}).toArray((err, result) => {
        if (err) throw err;

        client.close();

    });        
});