mongoose: detect if document inserted is a duplicate and if so, return the existing document

后端 未结 2 713
感情败类
感情败类 2021-02-13 13:47

This is my code:

    var thisValue = new models.Value({
        id:id,
        title:title //this is a unique value
    });

    console.log(thisValue);

    thi         


        
相关标签:
2条回答
  • 2021-02-13 14:02

    I really like WiredPrairie's answer, but his promise implementation is way too complicated.

    So, I decided to add my own promise implementation.

    Mongoose 3.8.x

    If you're using latest Mongoose 3.8.x then there is no need to use any other promise module, because since 3.8.0 model .create() method returns a promise:

    function saveNewValue(id, title) {
        return models.Value.create({
            id:id,
            title:title //this is a unique value
        }).then(null, function(err) {
            if (err.code === 11000) {
                return models.Value.findOne({title:title}).exec()
            } else {
                throw err;
            }
        });
    }
    
    saveNewValue('123', 'my title').then(function(doc) {
        // success
        console.log('success', doc);
    }, function(err) {
        // failure
        console.log('failure', err);
    });
    

    models.Value.findOne({title:title}).exec() also returns a promise, so there is no need for callbacks or any additional casting here.

    And if you don't normally use promises in your code, here is callback version of it:

    function saveNewValue(id, title, callback) {
        models.Value.create({
            id:id,
            title:title //this is a unique value
        }).then(null, function(err) {
            if (err.code === 11000) {
                return models.Value.findOne({title:title}).exec()
            } else {
                throw err;
            }
        }).onResolve(callback);
    }
    

    Previous versions of Mongoose

    If you're using any Mongoose version prior to 3.8.0, then you may need some help from when module:

    var when = require('when'),
        nodefn = require('when/node/function');
    
    function saveNewValue(id, title) {
        var thisValue = new models.Value({
            id:id,
            title:title //this is a unique value
        });
    
        var promise = nodefn.call(thisValue.save.bind(thisValue));
    
        return promise.spread(function(product, numAffected) {
            return product;
        }).otherwise(function(err) {
            if (err.code === 11000) {
                return models.Value.findOne({title:title}).exec()
            } else {
                throw err;
            }
        });
    }
    

    I'm using nodefn.call helper function to turn callback-styled .save() method into a promise. Mongoose team promised to add promises support to it in Mongoose 4.x.

    Then I'm using .spread helper method to extract the first argument from .save() callback.

    0 讨论(0)
  • 2021-02-13 14:08

    While your code doesn't handle a few error cases, and uses the wrong find function, the general flow is typical giving the work you want to do.

    1. If there are errors other than the duplicate, the callback isn't called, which likely will cause downstream issues in your NodeJs application
    2. use findOne rather than find as there will be only one result given the key is unique. Otherwise, it will return an array.
    3. If your callback expected the traditional error as the first argument, you could directly pass the callback to the findOne function rather than introducing an anonymous function.
    4. You also might want to look at findOneAndUpdate eventually, depending on what your final schema and logic will be.

    As mentioned, you might be able to use findOneAndUpdate, but with additional cost.

    function save(id, title, callback) {
        Value.findOneAndUpdate(
           {id: id, title: title}, /* query */
           {id: id, title: title}, /* update */
           { upsert: true}, /* create if it doesn't exist */
           callback);
    }
    

    There's still a callback of course, but it will write the data again if the duplicate is found. Whether that's an issue is really dependent on use cases.

    I've done a little clean-up of your code... but it's really quite simple and the callback should be clear. The callback to the function always receives either the newly saved document or the one that was matched as a duplicate. It's the responsibility of the function calling saveNewValue to check for an error and properly handle it. You'll see how I've also made certain that the callback is called regardless of type of error and is always called with the result in a consistent way.

    function saveNewValue(id, title, callback) {
        if (!callback) { throw new Error("callback required"); }
        var thisValue = new models.Value({
            id:id,
            title:title //this is a unique value
        });
    
        thisValue.save(function(err, product) {
            if (err) {
                if (err.code === 11000) { //error for dupes
                    return models.Value.findOne({title:title}, callback);
                }            
            }    
            callback(err, product);
        });
    }
    

    Alternatively, you could use the promise pattern. This example is using when.js.

    var when = require('when');
    
    function saveNewValue(id, title) {
        var deferred = when.defer();
    
        var thisValue = new models.Value({
            id:id,
            title:title //this is a unique value
        });
    
        thisValue.save(function(err, product) {
            if (err) {
                if (err.code === 11000) { //error for dupes
                    return models.Value.findOne({title:title}, function(err, val) {
                        if (err) {
                            return deferred.reject(err);
                        }
                        return deferred.resolve(val);
                    });
                }
                return deferred.reject(err);
            }
            return deferred.resolve(product);
        });
    
        return deferred.promise;
    }
    
    saveNewValue('123', 'my title').then(function(doc) {
        // success
    }, function(err) {
        // failure
    });
    
    0 讨论(0)
提交回复
热议问题