diff --git a/lib/collection.js b/lib/collection.js index 2adfe0a9c9..48da65678a 100644 --- a/lib/collection.js +++ b/lib/collection.js @@ -430,6 +430,56 @@ Collection.prototype.find = function() { define.classMethod('find', { callback: false, promise: false, returns: [Cursor] }); +/** + * + * @param {*} self + * @param {*} operation + * @param {*} args + * @param {*} options + */ +const executeOperation = (self, operation, args, options) => { + options = options || {}; + const Promise = self.s.promiseLibrary; + let resultMutator = options.resultMutator; + let callback = args[args.length - 1]; + + // Execute using callback + if (typeof callback === 'function') { + if (resultMutator) { + callback = args.pop(); + + if (options.resultCanBeError) { + args.push( + (err, result) => (err ? callback(err, result) : callback(null, resultMutator(result))) + ); + } else { + args.push( + (err, result) => (err ? callback(err, null) : callback(null, resultMutator(result))) + ); + } + } + + return operation.apply(null, args); + } + + // Return a Promise + return new Promise(function(resolve, reject) { + args[args.length - 1] = (err, r) => { + // NOTE: we check if `r == null` here explicitly because bulkWrite might have a bulkWriteError + if (options.resultCanBeError) { + if (err && r == null) return reject(err); + } else { + if (err) return reject(err); + } + + if (resultMutator) return resolve(resultMutator(r)); + resolve(r); + }; + + operation.apply(null, args); + }); +}; + /** * Inserts a single document into MongoDB. If documents passed in do not contain the **_id** field, * one will be added to each of the documents missing it by the driver, mutating the document. This behavior @@ -448,7 +498,6 @@ define.classMethod('find', { callback: false, promise: false, returns: [Cursor] * @return {Promise} returns Promise if no callback passed */ Collection.prototype.insertOne = function(doc, options, callback) { - var self = this; if (typeof options === 'function') (callback = options), (options = {}); options = options || {}; if (Array.isArray(doc) && typeof callback === 'function') { @@ -467,16 +516,7 @@ Collection.prototype.insertOne = function(doc, options, callback) { options.ignoreUndefined = this.s.options.ignoreUndefined; } - // Execute using callback - if (typeof callback === 'function') return insertOne(self, doc, options, callback); - - // Return a Promise - return new this.s.promiseLibrary(function(resolve, reject) { - insertOne(self, doc, options, function(err, r) { - if (err) return reject(err); - resolve(r); - }); - }); + return executeOperation(this, insertOne, [this, doc, options, callback]); }; var insertOne = function(self, doc, options, callback) { @@ -582,19 +622,9 @@ Collection.prototype.insertMany = function(docs, options, callback) { } ]; - // Execute using callback - if (typeof callback === 'function') - return bulkWrite(self, operations, options, function(err, r) { - if (err) return callback(err, r); - callback(null, mapInserManyResults(docs, r)); - }); - - // Return a Promise - return new this.s.promiseLibrary(function(resolve, reject) { - bulkWrite(self, operations, options, function(err, r) { - if (err) return reject(err); - resolve(mapInserManyResults(docs, r)); - }); + return executeOperation(this, bulkWrite, [this, operations, options, callback], { + resultMutator: result => mapInserManyResults(docs, result), + resultCanBeError: true }); }; @@ -653,7 +683,6 @@ define.classMethod('insertMany', { callback: true, promise: true }); * @return {Promise} returns Promise if no callback passed */ Collection.prototype.bulkWrite = function(operations, options, callback) { - var self = this; if (typeof options === 'function') (callback = options), (options = {}); options = options || { ordered: true }; @@ -661,15 +690,8 @@ Collection.prototype.bulkWrite = function(operations, options, callback) { throw MongoError.create({ message: 'operations must be an array of documents', driver: true }); } - // Execute using callback - if (typeof callback === 'function') return bulkWrite(self, operations, options, callback); - - // Return a Promise - return new this.s.promiseLibrary(function(resolve, reject) { - bulkWrite(self, operations, options, function(err, r) { - if (err && r == null) return reject(err); - resolve(r); - }); + return executeOperation(this, bulkWrite, [this, operations, options, callback], { + resultCanBeError: true }); }; @@ -941,8 +963,6 @@ define.classMethod('insert', { callback: true, promise: true }); * @return {Promise} returns Promise if no callback passed */ Collection.prototype.updateOne = function(filter, update, options, callback) { - var self = this; - if (typeof options === 'function') (callback = options), (options = {}); var err = checkForAtomicOperators(update); @@ -959,16 +979,7 @@ Collection.prototype.updateOne = function(filter, update, options, callback) { options.ignoreUndefined = this.s.options.ignoreUndefined; } - // Execute using callback - if (typeof callback === 'function') return updateOne(self, filter, update, options, callback); - - // Return a Promise - return new this.s.promiseLibrary(function(resolve, reject) { - updateOne(self, filter, update, options, function(err, r) { - if (err) return reject(err); - resolve(r); - }); - }); + return executeOperation(this, updateOne, [this, filter, update, options, callback]); }; var checkForAtomicOperators = function(update) { @@ -1022,7 +1033,6 @@ define.classMethod('updateOne', { callback: true, promise: true }); * @return {Promise} returns Promise if no callback passed */ Collection.prototype.replaceOne = function(filter, doc, options, callback) { - var self = this; if (typeof options === 'function') (callback = options), (options = {}); options = shallowClone(options); @@ -1032,16 +1042,7 @@ Collection.prototype.replaceOne = function(filter, doc, options, callback) { options.ignoreUndefined = this.s.options.ignoreUndefined; } - // Execute using callback - if (typeof callback === 'function') return replaceOne(self, filter, doc, options, callback); - - // Return a Promise - return new this.s.promiseLibrary(function(resolve, reject) { - replaceOne(self, filter, doc, options, function(err, r) { - if (err) return reject(err); - resolve(r); - }); - }); + return executeOperation(this, replaceOne, [this, filter, doc, options, callback]); }; var replaceOne = function(self, filter, doc, options, callback) { @@ -1084,7 +1085,6 @@ define.classMethod('replaceOne', { callback: true, promise: true }); * @return {Promise} returns Promise if no callback passed */ Collection.prototype.updateMany = function(filter, update, options, callback) { - var self = this; if (typeof options === 'function') (callback = options), (options = {}); var err = checkForAtomicOperators(update); @@ -1101,16 +1101,7 @@ Collection.prototype.updateMany = function(filter, update, options, callback) { options.ignoreUndefined = this.s.options.ignoreUndefined; } - // Execute using callback - if (typeof callback === 'function') return updateMany(self, filter, update, options, callback); - - // Return a Promise - return new this.s.promiseLibrary(function(resolve, reject) { - updateMany(self, filter, update, options, function(err, r) { - if (err) return reject(err); - resolve(r); - }); - }); + return executeOperation(this, updateMany, [this, filter, update, options, callback]); }; var updateMany = function(self, filter, update, options, callback) { @@ -1200,25 +1191,13 @@ var updateDocuments = function(self, selector, document, options, callback) { * @deprecated use updateOne, updateMany or bulkWrite */ Collection.prototype.update = function(selector, document, options, callback) { - var self = this; - // Add ignoreUndfined if (this.s.options.ignoreUndefined) { options = shallowClone(options); options.ignoreUndefined = this.s.options.ignoreUndefined; } - // Execute using callback - if (typeof callback === 'function') - return updateDocuments(self, selector, document, options, callback); - - // Return a Promise - return new this.s.promiseLibrary(function(resolve, reject) { - updateDocuments(self, selector, document, options, function(err, r) { - if (err) return reject(err); - resolve(r); - }); - }); + return executeOperation(this, updateDocuments, [this, selector, document, options, callback]); }; define.classMethod('update', { callback: true, promise: true }); @@ -1251,7 +1230,6 @@ define.classMethod('update', { callback: true, promise: true }); * @return {Promise} returns Promise if no callback passed */ Collection.prototype.deleteOne = function(filter, options, callback) { - var self = this; if (typeof options === 'function') (callback = options), (options = {}); options = shallowClone(options); @@ -1261,16 +1239,7 @@ Collection.prototype.deleteOne = function(filter, options, callback) { options.ignoreUndefined = this.s.options.ignoreUndefined; } - // Execute using callback - if (typeof callback === 'function') return deleteOne(self, filter, options, callback); - - // Return a Promise - return new this.s.promiseLibrary(function(resolve, reject) { - deleteOne(self, filter, options, function(err, r) { - if (err) return reject(err); - resolve(r); - }); - }); + return executeOperation(this, deleteOne, [this, filter, options, callback]); }; var deleteOne = function(self, filter, options, callback) { @@ -1302,7 +1271,6 @@ define.classMethod('removeOne', { callback: true, promise: true }); * @return {Promise} returns Promise if no callback passed */ Collection.prototype.deleteMany = function(filter, options, callback) { - var self = this; if (typeof options === 'function') (callback = options), (options = {}); options = shallowClone(options); @@ -1312,16 +1280,7 @@ Collection.prototype.deleteMany = function(filter, options, callback) { options.ignoreUndefined = this.s.options.ignoreUndefined; } - // Execute using callback - if (typeof callback === 'function') return deleteMany(self, filter, options, callback); - - // Return a Promise - return new this.s.promiseLibrary(function(resolve, reject) { - deleteMany(self, filter, options, function(err, r) { - if (err) return reject(err); - resolve(r); - }); - }); + return executeOperation(this, deleteMany, [this, filter, options, callback]); }; var deleteMany = function(self, filter, options, callback) { @@ -1394,24 +1353,13 @@ define.classMethod('removeMany', { callback: true, promise: true }); * @deprecated use deleteOne, deleteMany or bulkWrite */ Collection.prototype.remove = function(selector, options, callback) { - var self = this; - // Add ignoreUndfined if (this.s.options.ignoreUndefined) { options = shallowClone(options); options.ignoreUndefined = this.s.options.ignoreUndefined; } - // Execute using callback - if (typeof callback === 'function') return removeDocuments(self, selector, options, callback); - - // Return a Promise - return new this.s.promiseLibrary(function(resolve, reject) { - removeDocuments(self, selector, options, function(err, r) { - if (err) return reject(err); - resolve(r); - }); - }); + return executeOperation(this, removeDocuments, [this, selector, options, callback]); }; define.classMethod('remove', { callback: true, promise: true }); @@ -1430,7 +1378,6 @@ define.classMethod('remove', { callback: true, promise: true }); * @deprecated use insertOne, insertMany, updateOne or updateMany */ Collection.prototype.save = function(doc, options, callback) { - var self = this; if (typeof options === 'function') (callback = options), (options = {}); options = options || {}; @@ -1440,16 +1387,7 @@ Collection.prototype.save = function(doc, options, callback) { options.ignoreUndefined = this.s.options.ignoreUndefined; } - // Execute using callback - if (typeof callback === 'function') return save(self, doc, options, callback); - - // Return a Promise - return new this.s.promiseLibrary(function(resolve, reject) { - save(self, doc, options, function(err, r) { - if (err) return reject(err); - resolve(r); - }); - }); + return executeOperation(this, save, [this, doc, options, callback]); }; var save = function(self, doc, options, callback) { @@ -1512,21 +1450,11 @@ define.classMethod('save', { callback: true, promise: true }); * @return {Promise} returns Promise if no callback passed */ Collection.prototype.findOne = function() { - var self = this; var args = Array.prototype.slice.call(arguments, 0); var callback = args.pop(); if (typeof callback !== 'function') args.push(callback); - // Execute using callback - if (typeof callback === 'function') return findOne(self, args, callback); - - // Return a Promise - return new this.s.promiseLibrary(function(resolve, reject) { - findOne(self, args, function(err, r) { - if (err) return reject(err); - resolve(r); - }); - }); + return executeOperation(this, findOne, [this, args, callback]); }; var findOne = function(self, args, callback) { @@ -1561,20 +1489,10 @@ define.classMethod('findOne', { callback: true, promise: true }); * @return {Promise} returns Promise if no callback passed */ Collection.prototype.rename = function(newName, opt, callback) { - var self = this; if (typeof opt === 'function') (callback = opt), (opt = {}); opt = assign({}, opt, { readPreference: ReadPreference.PRIMARY }); - // Execute using callback - if (typeof callback === 'function') return rename(self, newName, opt, callback); - - // Return a Promise - return new this.s.promiseLibrary(function(resolve, reject) { - rename(self, newName, opt, function(err, r) { - if (err) return reject(err); - resolve(r); - }); - }); + return executeOperation(this, rename, [this, newName, opt, callback]); }; var rename = function(self, newName, opt, callback) { @@ -1624,20 +1542,14 @@ define.classMethod('rename', { callback: true, promise: true }); * @return {Promise} returns Promise if no callback passed */ Collection.prototype.drop = function(options, callback) { - var self = this; if (typeof options === 'function') (callback = options), (options = {}); options = options || {}; - // Execute using callback - if (typeof callback === 'function') - return self.s.db.dropCollection(self.s.name, options, callback); - // Return a Promise - return new this.s.promiseLibrary(function(resolve, reject) { - self.s.db.dropCollection(self.s.name, options, function(err, r) { - if (err) return reject(err); - resolve(r); - }); - }); + return executeOperation(this, this.s.db.dropCollection.bind(this.s.db), [ + this.s.name, + options, + callback + ]); }; define.classMethod('drop', { callback: true, promise: true }); @@ -1650,18 +1562,7 @@ define.classMethod('drop', { callback: true, promise: true }); * @return {Promise} returns Promise if no callback passed */ Collection.prototype.options = function(callback) { - var self = this; - - // Execute using callback - if (typeof callback === 'function') return options(self, callback); - - // Return a Promise - return new this.s.promiseLibrary(function(resolve, reject) { - options(self, function(err, r) { - if (err) return reject(err); - resolve(r); - }); - }); + return executeOperation(this, options, [this, callback]); }; var options = function(self, callback) { @@ -1688,18 +1589,7 @@ define.classMethod('options', { callback: true, promise: true }); * @return {Promise} returns Promise if no callback passed */ Collection.prototype.isCapped = function(callback) { - var self = this; - - // Execute using callback - if (typeof callback === 'function') return isCapped(self, callback); - - // Return a Promise - return new this.s.promiseLibrary(function(resolve, reject) { - isCapped(self, function(err, r) { - if (err) return reject(err); - resolve(r); - }); - }); + return executeOperation(this, isCapped, [this, callback]); }; var isCapped = function(self, callback) { @@ -1734,7 +1624,6 @@ define.classMethod('isCapped', { callback: true, promise: true }); * @return {Promise} returns Promise if no callback passed */ Collection.prototype.createIndex = function(fieldOrSpec, options, callback) { - var self = this; var args = Array.prototype.slice.call(arguments, 1); callback = args.pop(); if (typeof callback !== 'function') args.push(callback); @@ -1742,16 +1631,7 @@ Collection.prototype.createIndex = function(fieldOrSpec, options, callback) { options = typeof callback === 'function' ? options : callback; options = options == null ? {} : options; - // Execute using callback - if (typeof callback === 'function') return createIndex(self, fieldOrSpec, options, callback); - - // Return a Promise - return new this.s.promiseLibrary(function(resolve, reject) { - createIndex(self, fieldOrSpec, options, function(err, r) { - if (err) return reject(err); - resolve(r); - }); - }); + return executeOperation(this, createIndex, [this, fieldOrSpec, options, callback]); }; var createIndex = function(self, fieldOrSpec, options, callback) { @@ -1770,18 +1650,7 @@ define.classMethod('createIndex', { callback: true, promise: true }); * @return {Promise} returns Promise if no callback passed */ Collection.prototype.createIndexes = function(indexSpecs, callback) { - var self = this; - - // Execute using callback - if (typeof callback === 'function') return createIndexes(self, indexSpecs, callback); - - // Return a Promise - return new this.s.promiseLibrary(function(resolve, reject) { - createIndexes(self, indexSpecs, function(err, r) { - if (err) return reject(err); - resolve(r); - }); - }); + return executeOperation(this, createIndexes, [this, indexSpecs, callback]); }; var createIndexes = function(self, indexSpecs, callback) { @@ -1831,7 +1700,6 @@ define.classMethod('createIndexes', { callback: true, promise: true }); * @return {Promise} returns Promise if no callback passed */ Collection.prototype.dropIndex = function(indexName, options, callback) { - var self = this; var args = Array.prototype.slice.call(arguments, 1); callback = args.pop(); if (typeof callback !== 'function') args.push(callback); @@ -1839,16 +1707,7 @@ Collection.prototype.dropIndex = function(indexName, options, callback) { // Run only against primary options.readPreference = ReadPreference.PRIMARY; - // Execute using callback - if (typeof callback === 'function') return dropIndex(self, indexName, options, callback); - - // Return a Promise - return new this.s.promiseLibrary(function(resolve, reject) { - dropIndex(self, indexName, options, function(err, r) { - if (err) return reject(err); - resolve(r); - }); - }); + return executeOperation(this, dropIndex, [this, indexName, options, callback]); }; var dropIndex = function(self, indexName, options, callback) { @@ -1875,22 +1734,10 @@ define.classMethod('dropIndex', { callback: true, promise: true }); * @return {Promise} returns Promise if no callback passed */ Collection.prototype.dropIndexes = function(options, callback) { - var self = this; - - // Do we have options if (typeof options === 'function') (callback = options), (options = {}); options = options || {}; - // Execute using callback - if (typeof callback === 'function') return dropIndexes(self, options, callback); - - // Return a Promise - return new this.s.promiseLibrary(function(resolve, reject) { - dropIndexes(self, function(err, r) { - if (err) return reject(err); - resolve(r); - }); - }); + return executeOperation(this, dropIndexes, [this, options, callback]); }; var dropIndexes = function(self, options, callback) { @@ -1921,20 +1768,10 @@ define.classMethod('dropAllIndexes', { callback: true, promise: true }); * @return {Promise} returns Promise if no callback passed */ Collection.prototype.reIndex = function(options, callback) { - var self = this; if (typeof options === 'function') (callback = options), (options = {}); options = options || {}; - // Execute using callback - if (typeof callback === 'function') return reIndex(self, options, callback); - - // Return a Promise - return new this.s.promiseLibrary(function(resolve, reject) { - reIndex(self, options, function(err, r) { - if (err) return reject(err); - resolve(r); - }); - }); + return executeOperation(this, reIndex, [this, options, callback]); }; var reIndex = function(self, options, callback) { @@ -2026,20 +1863,10 @@ define.classMethod('listIndexes', { callback: false, promise: false, returns: [C * @return {Promise} returns Promise if no callback passed */ Collection.prototype.ensureIndex = function(fieldOrSpec, options, callback) { - var self = this; if (typeof options === 'function') (callback = options), (options = {}); options = options || {}; - // Execute using callback - if (typeof callback === 'function') return ensureIndex(self, fieldOrSpec, options, callback); - - // Return a Promise - return new this.s.promiseLibrary(function(resolve, reject) { - ensureIndex(self, fieldOrSpec, options, function(err, r) { - if (err) return reject(err); - resolve(r); - }); - }); + return executeOperation(this, ensureIndex, [this, fieldOrSpec, options, callback]); }; var ensureIndex = function(self, fieldOrSpec, options, callback) { @@ -2056,18 +1883,7 @@ define.classMethod('ensureIndex', { callback: true, promise: true }); * @return {Promise} returns Promise if no callback passed */ Collection.prototype.indexExists = function(indexes, callback) { - var self = this; - - // Execute using callback - if (typeof callback === 'function') return indexExists(self, indexes, callback); - - // Return a Promise - return new this.s.promiseLibrary(function(resolve, reject) { - indexExists(self, indexes, function(err, r) { - if (err) return reject(err); - resolve(r); - }); - }); + return executeOperation(this, indexExists, [this, indexes, callback]); }; var indexExists = function(self, indexes, callback) { @@ -2100,23 +1916,12 @@ define.classMethod('indexExists', { callback: true, promise: true }); * @return {Promise} returns Promise if no callback passed */ Collection.prototype.indexInformation = function(options, callback) { - var self = this; - // Unpack calls var args = Array.prototype.slice.call(arguments, 0); callback = args.pop(); if (typeof callback !== 'function') args.push(callback); options = args.length ? args.shift() || {} : {}; - // Execute using callback - if (typeof callback === 'function') return indexInformation(self, options, callback); - - // Return a Promise - return new this.s.promiseLibrary(function(resolve, reject) { - indexInformation(self, options, function(err, r) { - if (err) return reject(err); - resolve(r); - }); - }); + return executeOperation(this, indexInformation, [this, options, callback]); }; var indexInformation = function(self, options, callback) { @@ -2146,27 +1951,13 @@ define.classMethod('indexInformation', { callback: true, promise: true }); * @return {Promise} returns Promise if no callback passed */ Collection.prototype.count = function(query, options, callback) { - var self = this; var args = Array.prototype.slice.call(arguments, 0); callback = args.pop(); if (typeof callback !== 'function') args.push(callback); var queryOption = args.length ? args.shift() || {} : {}; var optionsOption = args.length ? args.shift() || {} : {}; - // Execute using callback - if (typeof callback === 'function') return count(self, queryOption, optionsOption, callback); - - // Check if query is empty - query = query || {}; - options = options || {}; - - // Return a Promise - return new this.s.promiseLibrary(function(resolve, reject) { - count(self, query, options, function(err, r) { - if (err) return reject(err); - resolve(r); - }); - }); + return executeOperation(this, count, [this, queryOption, optionsOption, callback]); }; var count = function(self, query, options, callback) { @@ -2220,28 +2011,13 @@ define.classMethod('count', { callback: true, promise: true }); * @return {Promise} returns Promise if no callback passed */ Collection.prototype.distinct = function(key, query, options, callback) { - var self = this; var args = Array.prototype.slice.call(arguments, 1); callback = args.pop(); if (typeof callback !== 'function') args.push(callback); var queryOption = args.length ? args.shift() || {} : {}; var optionsOption = args.length ? args.shift() || {} : {}; - // Execute using callback - if (typeof callback === 'function') - return distinct(self, key, queryOption, optionsOption, callback); - - // Ensure the query and options are set - query = query || {}; - options = options || {}; - - // Return a Promise - return new this.s.promiseLibrary(function(resolve, reject) { - distinct(self, key, query, options, function(err, r) { - if (err) return reject(err); - resolve(r); - }); - }); + return executeOperation(this, distinct, [this, key, queryOption, optionsOption, callback]); }; var distinct = function(self, key, query, options, callback) { @@ -2286,17 +2062,7 @@ define.classMethod('distinct', { callback: true, promise: true }); * @return {Promise} returns Promise if no callback passed */ Collection.prototype.indexes = function(callback) { - var self = this; - // Execute using callback - if (typeof callback === 'function') return indexes(self, callback); - - // Return a Promise - return new this.s.promiseLibrary(function(resolve, reject) { - indexes(self, function(err, r) { - if (err) return reject(err); - resolve(r); - }); - }); + return executeOperation(this, indexes, [this, callback]); }; var indexes = function(self, callback) { @@ -2315,23 +2081,13 @@ define.classMethod('indexes', { callback: true, promise: true }); * @return {Promise} returns Promise if no callback passed */ Collection.prototype.stats = function(options, callback) { - var self = this; var args = Array.prototype.slice.call(arguments, 0); callback = args.pop(); if (typeof callback !== 'function') args.push(callback); // Fetch all commands options = args.length ? args.shift() || {} : {}; - // Execute using callback - if (typeof callback === 'function') return stats(self, options, callback); - - // Return a Promise - return new this.s.promiseLibrary(function(resolve, reject) { - stats(self, options, function(err, r) { - if (err) return reject(err); - resolve(r); - }); - }); + return executeOperation(this, stats, [this, options, callback]); }; var stats = function(self, options, callback) { @@ -2380,7 +2136,6 @@ define.classMethod('stats', { callback: true, promise: true }); * @return {Promise} returns Promise if no callback passed */ Collection.prototype.findOneAndDelete = function(filter, options, callback) { - var self = this; if (typeof options === 'function') (callback = options), (options = {}); options = options || {}; @@ -2388,18 +2143,7 @@ Collection.prototype.findOneAndDelete = function(filter, options, callback) { if (filter == null || typeof filter !== 'object') throw toError('filter parameter must be an object'); - // Execute using callback - if (typeof callback === 'function') return findOneAndDelete(self, filter, options, callback); - - // Return a Promise - return new this.s.promiseLibrary(function(resolve, reject) { - options = options || {}; - - findOneAndDelete(self, filter, options, function(err, r) { - if (err) return reject(err); - resolve(r); - }); - }); + return executeOperation(this, findOneAndDelete, [this, filter, options, callback]); }; var findOneAndDelete = function(self, filter, options, callback) { @@ -2429,7 +2173,6 @@ define.classMethod('findOneAndDelete', { callback: true, promise: true }); * @return {Promise} returns Promise if no callback passed */ Collection.prototype.findOneAndReplace = function(filter, replacement, options, callback) { - var self = this; if (typeof options === 'function') (callback = options), (options = {}); options = options || {}; @@ -2439,19 +2182,7 @@ Collection.prototype.findOneAndReplace = function(filter, replacement, options, if (replacement == null || typeof replacement !== 'object') throw toError('replacement parameter must be an object'); - // Execute using callback - if (typeof callback === 'function') - return findOneAndReplace(self, filter, replacement, options, callback); - - // Return a Promise - return new this.s.promiseLibrary(function(resolve, reject) { - options = options || {}; - - findOneAndReplace(self, filter, replacement, options, function(err, r) { - if (err) return reject(err); - resolve(r); - }); - }); + return executeOperation(this, findOneAndReplace, [this, filter, replacement, options, callback]); }; var findOneAndReplace = function(self, filter, replacement, options, callback) { @@ -2485,7 +2216,6 @@ define.classMethod('findOneAndReplace', { callback: true, promise: true }); * @return {Promise} returns Promise if no callback passed */ Collection.prototype.findOneAndUpdate = function(filter, update, options, callback) { - var self = this; if (typeof options === 'function') (callback = options), (options = {}); options = options || {}; @@ -2495,19 +2225,7 @@ Collection.prototype.findOneAndUpdate = function(filter, update, options, callba if (update == null || typeof update !== 'object') throw toError('update parameter must be an object'); - // Execute using callback - if (typeof callback === 'function') - return findOneAndUpdate(self, filter, update, options, callback); - - // Return a Promise - return new this.s.promiseLibrary(function(resolve, reject) { - options = options || {}; - - findOneAndUpdate(self, filter, update, options, function(err, r) { - if (err) return reject(err); - resolve(r); - }); - }); + return executeOperation(this, findOneAndUpdate, [this, filter, update, options, callback]); }; var findOneAndUpdate = function(self, filter, update, options, callback) { @@ -2544,7 +2262,6 @@ define.classMethod('findOneAndUpdate', { callback: true, promise: true }); * @deprecated use findOneAndUpdate, findOneAndReplace or findOneAndDelete instead */ Collection.prototype.findAndModify = function(query, sort, doc, options, callback) { - var self = this; var args = Array.prototype.slice.call(arguments, 1); callback = args.pop(); if (typeof callback !== 'function') args.push(callback); @@ -2557,19 +2274,7 @@ Collection.prototype.findAndModify = function(query, sort, doc, options, callbac // Force read preference primary options.readPreference = ReadPreference.PRIMARY; - // Execute using callback - if (typeof callback === 'function') - return findAndModify(self, query, sort, doc, options, callback); - - // Return a Promise - return new this.s.promiseLibrary(function(resolve, reject) { - options = options || {}; - - findAndModify(self, query, sort, doc, options, function(err, r) { - if (err) return reject(err); - resolve(r); - }); - }); + return executeOperation(this, findAndModify, [this, query, sort, doc, options, callback]); }; var findAndModify = function(self, query, sort, doc, options, callback) { @@ -2653,23 +2358,13 @@ define.classMethod('findAndModify', { callback: true, promise: true }); * @deprecated use findOneAndDelete instead */ Collection.prototype.findAndRemove = function(query, sort, options, callback) { - var self = this; var args = Array.prototype.slice.call(arguments, 1); callback = args.pop(); if (typeof callback !== 'function') args.push(callback); sort = args.length ? args.shift() || [] : []; options = args.length ? args.shift() || {} : {}; - // Execute using callback - if (typeof callback === 'function') return findAndRemove(self, query, sort, options, callback); - - // Return a Promise - return new this.s.promiseLibrary(function(resolve, reject) { - findAndRemove(self, query, sort, options, function(err, r) { - if (err) return reject(err); - resolve(r); - }); - }); + return executeOperation(this, findAndRemove, [this, query, sort, options, callback]); }; var findAndRemove = function(self, query, sort, options, callback) { @@ -2910,7 +2605,6 @@ define.classMethod('watch', { callback: false, promise: false }); * @return {Promise} returns Promise if no callback passed */ Collection.prototype.parallelCollectionScan = function(options, callback) { - var self = this; if (typeof options === 'function') (callback = options), (options = { numCursors: 1 }); // Set number of cursors to 1 options.numCursors = options.numCursors || 1; @@ -2923,16 +2617,7 @@ Collection.prototype.parallelCollectionScan = function(options, callback) { // Add a promiseLibrary options.promiseLibrary = this.s.promiseLibrary; - // Execute using callback - if (typeof callback === 'function') return parallelCollectionScan(self, options, callback); - - // Return a Promise - return new this.s.promiseLibrary(function(resolve, reject) { - parallelCollectionScan(self, options, function(err, r) { - if (err) return reject(err); - resolve(r); - }); - }); + return executeOperation(this, parallelCollectionScan, [this, options, callback]); }; var parallelCollectionScan = function(self, options, callback) { @@ -2999,7 +2684,6 @@ define.classMethod('parallelCollectionScan', { callback: true, promise: true }); * @return {Promise} returns Promise if no callback passed */ Collection.prototype.geoNear = function(x, y, options, callback) { - var self = this; var point = typeof x === 'object' && x, args = Array.prototype.slice.call(arguments, point ? 1 : 2); @@ -3008,16 +2692,7 @@ Collection.prototype.geoNear = function(x, y, options, callback) { // Fetch all commands options = args.length ? args.shift() || {} : {}; - // Execute using callback - if (typeof callback === 'function') return geoNear(self, x, y, point, options, callback); - - // Return a Promise - return new this.s.promiseLibrary(function(resolve, reject) { - geoNear(self, x, y, point, options, function(err, r) { - if (err) return reject(err); - resolve(r); - }); - }); + return executeOperation(this, geoNear, [this, x, y, point, options, callback]); }; var geoNear = function(self, x, y, point, options, callback) { @@ -3077,23 +2752,13 @@ define.classMethod('geoNear', { callback: true, promise: true }); * @return {Promise} returns Promise if no callback passed */ Collection.prototype.geoHaystackSearch = function(x, y, options, callback) { - var self = this; var args = Array.prototype.slice.call(arguments, 2); callback = args.pop(); if (typeof callback !== 'function') args.push(callback); // Fetch all commands options = args.length ? args.shift() || {} : {}; - // Execute using callback - if (typeof callback === 'function') return geoHaystackSearch(self, x, y, options, callback); - - // Return a Promise - return new this.s.promiseLibrary(function(resolve, reject) { - geoHaystackSearch(self, x, y, options, function(err, r) { - if (err) return reject(err); - resolve(r); - }); - }); + return executeOperation(this, geoHaystackSearch, [this, x, y, options, callback]); }; var geoHaystackSearch = function(self, x, y, options, callback) { @@ -3187,7 +2852,6 @@ Collection.prototype.group = function( options, callback ) { - var self = this; var args = Array.prototype.slice.call(arguments, 3); callback = args.pop(); if (typeof callback !== 'function') args.push(callback); @@ -3223,16 +2887,17 @@ Collection.prototype.group = function( // Set up the command as default command = command == null ? true : command; - // Execute using callback - if (typeof callback === 'function') - return group(self, keys, condition, initial, reduce, finalize, command, options, callback); - // Return a Promise - return new this.s.promiseLibrary(function(resolve, reject) { - group(self, keys, condition, initial, reduce, finalize, command, options, function(err, r) { - if (err) return reject(err); - resolve(r); - }); - }); + return executeOperation(this, group, [ + this, + keys, + condition, + initial, + reduce, + finalize, + command, + options, + callback + ]); }; var group = function(self, keys, condition, initial, reduce, finalize, command, options, callback) { diff --git a/lib/utils.js b/lib/utils.js index fe1aff7b3d..7d81428fef 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -376,6 +376,61 @@ var mergeOptionsAndWriteConcern = function(targetOptions, sourceOptions, keys, m return targetOptions; }; +/** + * Executes the given operation with provided arguments. + * + * This method reduces large amounts of duplication in the entire codebase by providing + * a single point for determining whether callbacks or promises should be used. Additionally + * it allows for a single point of entry to provide features such as implicit sessions, which + * are required by the Driver Sessions specification in the event that a ClientSession is + * not provided + * + * @param {object} self The originating class for operation execution, used primarily to find a refernce to the Promise library + * @param {function} operation The operation to execute + * @param {array} args Arguments to apply the provided operation + * @param {object} [options] Options that modify the behavior of the method + * @param {function]} [options.resultMutator] Allows for the result of the operation to be changed for custom return types + */ +const executeOperation = (self, operation, args, options) => { + if (!Array.isArray(args)) { + throw new TypeError('This method requires an array of arguments to apply'); + } + + options = options || {}; + const Promise = self.s.promiseLibrary; + let resultMutator = options.resultMutator; + let callback = args[args.length - 1]; + + // Execute using callback + if (typeof callback === 'function') { + if (resultMutator) { + callback = args.pop(); + + args.push( + (err, result) => (err ? callback(err, null) : callback(null, resultMutator(result))) + ); + } + + return operation.apply(null, args); + } + + // Return a Promise + if (args[args.length - 1] != null) { + console.dir(operation.name); + throw new TypeError('final argument to `executeOperation` must be a callback'); + } + + return new Promise(function(resolve, reject) { + args[args.length - 1] = (err, r) => { + if (err) return reject(err); + if (resultMutator) return resolve(resultMutator(r)); + resolve(r); + }; + + operation.apply(null, args); + }); +}; + exports.filterOptions = filterOptions; exports.mergeOptions = mergeOptions; exports.translateOptions = translateOptions; @@ -394,3 +449,4 @@ exports.MAX_JS_INT = 0x20000000000000; exports.assign = assign; exports.mergeOptionsAndWriteConcern = mergeOptionsAndWriteConcern; exports.getReadPreference = getReadPreference; +exports.executeOperation = executeOperation;