Sreedhar M B
Sreedhar M B

Reputation: 189

How to implement map function of Mongodb cursor in node.js (node-mondodb-native)

I am trying to implement following MongoDB query in NodeJS

db.tvseries.find({}).map(function(doc){
    var userHasSubscribed = false;

    doc.followers && doc.followers.forEach(function(follower) {
            if(follower.$id == "abc") {
                userHasSubscribed = true;
            }
        });


    var followers = doc.followers && doc.followers.map(function(follower) {
            var followerObj;
            db[follower.$ref].find({
                    "_id" : follower.$id
                }).map(function(userObj) {
                        followerObj = userObj;
                    });
             return followerObj;
        });

    return {
            "id": doc.name,
            "userHasSubscribed": userHasSubscribed,
            "followers": followers || []
        };
})

Following is the db

users collection

{
     "id": ObjectId("abc"),
     "name": "abc_name"
},
{
     "id": ObjectId("def"),
     "name": "def_name"
},
{
     "id": ObjectId("ijk"),
     "name": "ijk_name"
}

tvseries collection

{
     "id": ObjectId("123"),
     "name": "123_name",
     "followers": [
        {
            "$ref": "users",
            "$id": ObjectId("abc"),
        },
        {
            "$ref": "users",
            "$id": ObjectId("def"),
        }
     ]
},
{
     "id": ObjectId("456"),
     "name": "456_name",
     "followers": [
         {
            "$ref": "users",
            "$id": ObjectId("ijk"),
        },
     ]
},
{
     "id": ObjectId("789"),
     "name": "789_name"
}

I am not able to figure out how to execute the above MongoDB query in NodeJS with the help of node-mongodb-native plugin.

I tried the below code but then I get TypeError: undefined is not a function at .map

var collection = db.collection('users');
collection.find({}).map(function(doc) {
   console.log(doc);
});

How to execute .map function in NodeJS?

Thanks in advance

Upvotes: 2

Views: 7548

Answers (4)

CervEd
CervEd

Reputation: 4263

map returns a cursor, toArray returns a Promise that will execute a cursor and return it's results. That may be an array of the original query find, limit etc. or a promise of an array of those result piped through a function.

This is typically useful when you want to take the documents of the cursor and process that (maybe fetch something else) while the cursor is still fetching documents, as opposed to waiting until they have all been fetched to node memory

Consider the example

let foos = await db.collection("foos")
    .find()
    .project({
        barId: 1
    })
    .toArray() // returns a Promise<{barId: ObjectId}[]>

// we now have all foos into memory, time to get bars
let bars = await Promise.all(foos.map(doc => db
    .collection("bars")
    .findOne({
        _id: doc.barId
    })))

this is roughly equivalent to

bars = await db.collection("foos")
    .find()
    .project({
        barId: 1
    })
    .toArray() // returns a Promise<{barId: ObjectId}[]>
    .then(docs => docs
        .map(doc => db
            .collection("bars")
            .findOne({
                _id: doc.barId
            })))

using map you can perform the operation asynchrounsly and (hopefully) more efficiently

bars = await db.collection("foos")
    .find()
    .project({
        barId: 1
    })
    .map(doc => db
        .collection("bars")
        .findOne({
            _id: doc.barId
        }))
    .toArray()
    .then(barPromises => Promise.all(barPromises)) // Promise<Bar[]>

The main point is that map is simply a function to be applied to the results fetched by the cursor. That function won't get executed until you turn it into a Promise, using either forEach or more sensibly, map

Upvotes: 0

JP Lew
JP Lew

Reputation: 4439

To echo @bamse's anwer, I got it working with .toArray(). Here is an async example:

async function getWordArray (query) {
  const client = await MongoClient.connect(url)
  const collection = client.db('personal').collection('wordBank')
  const data = await collection.find(query).map(doc => doc.word).toArray()
  return data
}

Then I use it in my Express route like this:

app.get('/search/:fragment', asyncMiddleware(async (req, res, next) => {
  const result = await getWordArray({word: 'boat'})
  res.json(result)
}))

Finally, if you need a guide to async/await middleware in NodeJS, here is a guide: https://medium.com/@Abazhenov/using-async-await-in-express-with-node-8-b8af872c0016

Upvotes: 3

bamse
bamse

Reputation: 4383

I struggled with this for some time. I found that by adding .toArray() after the map function works.

You could even skip map and only add .toArray() to get all the documents fields.

  const accounts = await _db
    .collection('accounts')
    .find()
    .map(v => v._id) // leaving this out gets you all the fields
    .toArray();

  console.log(accounts); // [{_id: xxx}, {_id: xxx} ...]

Please take note that in order for map to work the function used must return something - your example only console.logs without returning a value.


The forEach solution works but I really wanted map to work.

Upvotes: 8

midudev
midudev

Reputation: 1061

I know that I'm pretty late but I've arrived here by searching on Google about the same problem. Finally, I wasn't able to use map function to do it, but using forEach did the trick.

An example using ES6 and StandardJS.

  let ids = []
  let PublicationId = ObjectID(id)
  feeds_collection
    .find({PublicationId})
    .project({ _id: 1 })
    .forEach((feed) => {
      ids.push(feed._id)
    }, () => done(ids))

Upvotes: 5

Related Questions