46

I'm trying to read data from a MongoDB Atlas collection using Node.js. When I try to read the contents of my collection I get the error MongoError: Cannot use a session that has ended. Here is my code

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

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

I'm able to query for a specific document, but I'm not sure how to return all documents of a collection. I've searched for this error, I can't find much on it. Thanks

awebdev
  • 997
  • 2
  • 10
  • 27

5 Answers5

71

In your code, it doesn't wait for the find() to complete its execution and goes on to the client.close() statement. So by the time it tries to read data from the db, the connection has already ended. I faced this same problem and solved it like this:

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

EDIT: this whole thing should be in an async function.

Rohit
  • 1,385
  • 2
  • 15
  • 21
  • 2
    What I meant to say is that you should write this in an async function because you are using await. Like as follows: ```const findItems = async () => { // write above code here };``` – Rohit Mar 18 '20 at 15:42
3

Ran into the same issue when I updated the MongoClient from 3.3.2 to the latest version (3.5.2 as of this writing.) Either install only 3.3.2 version by changing the package.json "mongodb": "3.3.2", or just use async and await wrapper.

If still the issue persists, remove the node_modules and install again.

Ravi Teja
  • 119
  • 1
  • 2
  • 13
  • your solution worked for me too, I triple checked all my async await functions and was still getting the same error until I downgraded my version to 3.3.2. Thanks! – mpc75 Jan 29 '20 at 19:54
3

One option is to use aPromise chain. collection.find({}).toArray() can either receive a callback function or return a promise, so you can chain calls with .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
);

Of course, this daisy chaining makes the code more synchronous. This is useful when the next call in the chain relates to the previous one, like getting a user id in the first one, then looking up user detail in the next.

Several unrelated queries should be executed in parallel (async) and when all the results are back, dispose of the database connection. You could do this by tracking each call in an array or counter, for example.

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

You have 3 queries. As each one invokes dispose() the counter increments. When they've all invoked dispose(), the last one will also close the connection.

Async/Await should make it even easier, because they unwrap the Promise result from the then function.

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

Below is an example of how Async/Await can end up making async code behave sequentially and run inefficiently by waiting for one async function to complete before invoking the next one, when the ideal scenario is to invoke them all immediately and only wait until they all are complete.

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

Note: Awaiting like in the example above makes the calls sequential again. If each takes 2 seconds to run, the function will take 6 seconds to complete.

Combining the best of all worlds, you would want to use Async/Await while running all calls immediately. Fortunately, Promise has a method to do this, so test() can be written like this: -

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

Here's a working example to demonstrate the difference in performance: -

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

other people have touched on this but I just want to highlight that .toArray() is executed asynchronously so you need to make sure that it has finished before closing the session

this won't work

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

this will

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

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

        client.close();

    });        
});
antelove
  • 3,216
  • 26
  • 20