0

I want to save multiple documents which are tags found on a post indicated by hash sign '#'. I have tags in an array. for example:

var tags = ['banana', 'apple', 'orange', 'pie']

I loop over them and convert the to document objects (to insert them into DB). the problem is that I want to insert a new document to collection only if it never inserted before. and if it inserted before I want to increment inserted document's userCounter property by one.

var tags = ['banana', 'apple', 'pie', 'orange'];

var docs = tags.map(function(tagTitle) {
  return {
    title: tagTitle,
    // useCounter: ??????????
  };
});

var Hashtag = require('./models/Hashtag');

/**
* it creates multiple documents even if a document already exists in the collection,
* I want to increment useCounter property of each document if they exist in the collection;
* not creating new ones.
* for example if a document with title ptoperty of 'banana' is inserted before, now increment
* document useCounter value by one. and if apple never inserted to collection, create a new document 
* with title of 'apple' and set '1' as its initial useCounter value
*/

Hashtag.create(docs)
.then(function(createdDocs) {

})
.then(null, function(err) {
  //handle errors
});

2 Answers2

1
async function findOrIncrease(title) {      
   let hashtag = await Hashtag.findOne({title});
   if(hashtag) {
     hashtag.userCounter++;
   } else {
     hashtag = new Hashtag({title, userCounter: 1});
   }
   await hashtag.save();
 }

Usable as:

  (async function() {
    for(const title of tags)
      await findOrIncrease(title);
  })()

Or if you want to execute all in parallel:

  tags.forEach(findOrIncrease);

You can speed that up by using mongodbs indexes.

Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151
0

Thanks @Jonas W for response, Also there is another solution I found. I think it might be better (because it's clearer and faster in performance) to make some promises based on tags array and resolve tag document from these promises (or reject them with some reasons). Then use Promise.all() to make a fullfilled promise providing mongoose documents (created or updated based on some conditions). It's something like this:

// some other chains
.then((xxxx) => {
        const hashtagsTitles = require('../../src/hashtags/hashtagParser').hashtags(req.newPost.caption);
        const Hashtag = require('../../src/database/models/Hashtag');

        let findOrIncrease = title =>
            new Promise((resolve, reject) => {
                Hashtag.findOne({
                        title
                    })
                    .then((hashtag) => {
                        if (!hashtag) {
                            new Hashtag({
                                    title,
                                    usageCount: 0
                                }).save()
                                .then(hashtag => resolve(hashtag._id))
                                .catch(err => reject(err));
                        } else {
                            hashtag.usageCount++;
                            hashtag.save()
                                .then(hashtag => resolve(hashtag._id))
                                .catch(err => reject(err));
                        }
                    })
                    .catch(err => reject(err));
            });

        let promiseArr = hashtagsTitles.map((hashtagTitle) =>
            findOrIncrease(hashtagTitle)
        );

        return Promise.all(promiseArr)
            .then(results => results)
            .catch(err => {
                throw err
            });
    })
    .then((hashtags) => {
        hashtags.forEach((hashtag) => req.newPost.hashtags.push(hashtag));
    })
    //there might be some other chains

Also there is a good guide here: Mongoose - Create document if not exists, otherwise, update- return document in either case

  • https://stackoverflow.com/questions/23803743/what-is-the-explicit-promise-construction-antipattern-and-how-do-i-avoid-it – Jonas Wilms Apr 11 '18 at 15:51
  • I read this post and some others on github. Only thing they noticed as a harm is unhandled errors which I handle them (as you can see in the code). Also I think it is not that complicated. Its just a promise which resolves to a value within the context of some other promises (which might provided by an api) handlers. Would you please tell me what is the main reason that it' an antipattern? –  Apr 11 '18 at 17:47
  • well you could just chain the promise which makes it more readable. And actually this does *exactly* the same then my code above, just in a few more lines :) – Jonas Wilms Apr 11 '18 at 18:35
  • Using your code how to push each created (or increased) document's ID to an empty array? –  Apr 11 '18 at 18:48
  • `Promise.all(tags.map(findOrIncrease))` – Jonas Wilms Apr 11 '18 at 19:16
  • true, that works. but how about performance? isn't any way to achieve a better speed? (in performance manner). You consider, now there are only two tags exist in DB collection and it takes about ~7000-11000 ms to complete the tasks. (in none-antipattern way :)) –  Apr 12 '18 at 01:04
  • again: my answer is *exactly* the same, therefore there will be no performance difference. – Jonas Wilms Apr 12 '18 at 05:23