Mangouste: détecte si le document inséré est un doublon et si c’est le cas, renvoie le document existant

Ceci est mon code:

var thisValue = new models.Value({ id:id, title:title //this is a unique value }); console.log(thisValue); thisValue.save(function(err, product, numberAffected) { if (err) { if (err.code === 11000) { //error for dupes console.error('Duplicate blocked!'); models.Value.find({title:title}, function(err, docs) { callback(docs) //this is ugly }); } return; } console.log('Value saved:', product); if (callback) { callback(product); } }); 

Si je détecte qu’un doublon tente d’être inséré, je le bloque. Cependant, lorsque cela se produit, je souhaite renvoyer le document existant. Comme vous pouvez le voir, j’ai implémenté une chaîne de rappels, mais celle-ci est laide et imprévisible (c.-à-d. Comment savoir quel rappel sera appelé? Comment passer dans le bon?). Est-ce que quelqu’un sait comment résoudre ce problème? Toute aide appréciée.

Bien que votre code ne gère pas quelques erreurs et utilise la fonction de find incorrecte, le stream général est typique du travail que vous souhaitez effectuer.

  1. S’il existe des erreurs autres que la copie, le rappel n’est pas appelé, ce qui entraînera probablement des problèmes en aval dans votre application NodeJs.
  2. utilisez findOne plutôt que find car il n’y aura qu’un seul résultat étant donné que la clé est unique. Sinon, il retournera un tableau.
  3. Si votre rappel attendait l’ error traditionnelle comme premier argument, vous pouvez le transmettre directement à la fonction findOne plutôt que d’introduire une fonction anonyme.
  4. Vous pouvez également consulter éventuellement findOneAndUpdate , en fonction de votre schéma final et de votre logique.

Comme mentionné, vous pourrez peut-être utiliser findOneAndUpdate , mais moyennant des frais supplémentaires.

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

Bien sûr, il y a toujours un rappel, mais il réécrira les données si le doublon est trouvé. Que ce soit un problème dépend vraiment des cas d’utilisation.

J’ai fait un peu de nettoyage de votre code … mais c’est vraiment très simple et le rappel doit être clair. Le callback de la fonction reçoit toujours le document nouvellement enregistré ou celui qui a été mis en correspondance. C’est la fonction qui appelle saveNewValue qui est saveNewValue de rechercher une erreur et de la gérer correctement. Vous verrez comment j’ai également veillé à ce que le rappel soit appelé quel que soit le type d’erreur et qu’il soit toujours appelé avec le résultat de manière cohérente.

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

Alternativement, vous pouvez utiliser le modèle de promesse . Cet exemple utilise 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 }); 

J’aime beaucoup la réponse de WiredPrairie , mais la mise en œuvre de sa promesse est bien trop compliquée.

J’ai donc décidé d’append ma propre implémentation de promesse.

Mangouste 3.8.x

Si vous utilisez la dernière version de Mongoose 3.8.x aucun autre module de promesse n’est nécessaire, car depuis la 3.8.0 modèle .create() méthode .create() renvoie une promesse:

 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() renvoie également une promesse, il n’y a donc pas besoin de rappels ni de transtypage supplémentaire ici.

Et si vous n’utilisez pas normalement de promesses dans votre code, en voici la version de rappel:

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

Versions précédentes de Mongoose

Si vous utilisez une version de Mongoose antérieure à la version 3.8.0 , vous aurez peut-être besoin d’aide de la part du module when :

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

J’utilise la fonction d’assistance nodefn.call pour transformer la méthode .save() style .save() en promesse. L’équipe de Mongoose a promis d’append des promesses de soutien à Mongoose 4.x

Ensuite, .spread méthode d’assistance .spread pour extraire le premier argument du rappel .save() .