diff --git a/.eslintrc b/.eslintrc index 2137437304a..da04e546911 100644 --- a/.eslintrc +++ b/.eslintrc @@ -16,7 +16,8 @@ "ecmaVersion": 2017 }, "plugins": [ - "prettier" + "prettier", + "promise" ], "rules": { "prettier/prettier": ["error", { @@ -24,7 +25,7 @@ "tabWidth": 2, "printWidth": 100 }], - + "promise/no-native": "error", "no-console": 0, "eqeqeq": ["error", "always", {"null": "ignore"}], "strict": ["error", "global"] diff --git a/lib/admin.js b/lib/admin.js index d2f7d990f28..5115115ab3d 100644 --- a/lib/admin.js +++ b/lib/admin.js @@ -1,7 +1,7 @@ 'use strict'; +const PromiseProvider = require('./promise_provider'); const applyWriteConcern = require('./utils').applyWriteConcern; - const AddUserOperation = require('./operations/add_user'); const ExecuteDbAdminCommandOperation = require('./operations/execute_db_admin_command'); const RemoveUserOperation = require('./operations/remove_user'); @@ -42,14 +42,14 @@ const executeOperation = require('./operations/execute_operation'); * @class * @return {Admin} a collection instance. */ -function Admin(db, topology, promiseLibrary) { +function Admin(db, topology) { if (!(this instanceof Admin)) return new Admin(db, topology); // Internal state this.s = { db: db, topology: topology, - promiseLibrary: promiseLibrary + promiseLibrary: PromiseProvider.get(db, topology) }; } diff --git a/lib/async/async_iterator.js b/lib/async/async_iterator.js index 6021aadf9ec..ebfaee095f6 100644 --- a/lib/async/async_iterator.js +++ b/lib/async/async_iterator.js @@ -1,5 +1,7 @@ 'use strict'; +const PromiseProvider = require('../promise_provider'); + // async function* asyncIterator() { // while (true) { // const value = await this.next(); @@ -14,6 +16,7 @@ // TODO: change this to the async generator function above function asyncIterator() { + const Promise = PromiseProvider.get(); const cursor = this; return { diff --git a/lib/bulk/common.js b/lib/bulk/common.js index 16aad66adbd..0ca45c8aa27 100644 --- a/lib/bulk/common.js +++ b/lib/bulk/common.js @@ -1,5 +1,6 @@ 'use strict'; +const PromiseProvider = require('../promise_provider'); const Long = require('../core').BSON.Long; const MongoError = require('../core').MongoError; const ObjectID = require('../core').BSON.ObjectID; @@ -775,7 +776,7 @@ class BulkOperationBase { const writeConcern = finalOptions.writeConcern; // Get the promiseLibrary - const promiseLibrary = options.promiseLibrary || Promise; + const promiseLibrary = PromiseProvider.get(options, collection, topology); // Final results const bulkResult = { @@ -1022,12 +1023,13 @@ class BulkOperationBase { * @param {*} callback */ _handleEarlyError(err, callback) { + const Promise = PromiseProvider.get(this); if (typeof callback === 'function') { callback(err, null); return; } - return this.s.promiseLibrary.reject(err); + return Promise.reject(err); } /** diff --git a/lib/change_stream.js b/lib/change_stream.js index a5b7219a8bf..d381e981ac2 100644 --- a/lib/change_stream.js +++ b/lib/change_stream.js @@ -1,5 +1,6 @@ 'use strict'; +const PromiseProvider = require('./promise_provider'); const EventEmitter = require('events'); const isResumableError = require('./error').isResumableError; const MongoError = require('./core').MongoError; @@ -85,7 +86,7 @@ class ChangeStream extends EventEmitter { ); } - this.promiseLibrary = parent.s.promiseLibrary; + this.promiseLibrary = PromiseProvider.get(options, parent); if (!this.options.readPreference && parent.s.readPreference) { this.options.readPreference = parent.s.readPreference; } @@ -138,10 +139,11 @@ class ChangeStream extends EventEmitter { * @return {Promise} returns Promise if no callback passed */ next(callback) { - var self = this; + const self = this; + const Promise = PromiseProvider.get(self); if (this.isClosed()) { if (callback) return callback(new Error('Change Stream is not open.'), null); - return self.promiseLibrary.reject(new Error('Change Stream is not open.')); + return Promise.reject(new Error('Change Stream is not open.')); } return this.cursor @@ -169,9 +171,11 @@ class ChangeStream extends EventEmitter { * @return {Promise} returns Promise if no callback passed */ close(callback) { + const Promise = PromiseProvider.get(this); + if (!this.cursor) { if (callback) return callback(); - return this.promiseLibrary.resolve(); + return Promise.resolve(); } // Tidy up the existing cursor @@ -186,7 +190,7 @@ class ChangeStream extends EventEmitter { }); } - const PromiseCtor = this.promiseLibrary || Promise; + const PromiseCtor = PromiseProvider.get(this); return new PromiseCtor((resolve, reject) => { cursor.close(err => { ['data', 'close', 'end', 'error'].forEach(event => cursor.removeAllListeners(event)); @@ -472,6 +476,7 @@ function waitForTopologyConnected(topology, options, callback) { // Handle new change events. This method brings together the routes from the callback, event emitter, and promise ways of using ChangeStream. function processNewChange(args) { const changeStream = args.changeStream; + const Promise = PromiseProvider.get(changeStream); const error = args.error; const change = args.change; const callback = args.callback; @@ -486,9 +491,7 @@ function processNewChange(args) { } const error = new MongoError('ChangeStream is closed'); - return typeof callback === 'function' - ? callback(error, null) - : changeStream.promiseLibrary.reject(error); + return typeof callback === 'function' ? callback(error, null) : Promise.reject(error); } const topology = changeStream.topology; @@ -546,7 +549,7 @@ function processNewChange(args) { if (eventEmitter) return changeStream.emit('error', error); if (typeof callback === 'function') return callback(error, null); - return changeStream.promiseLibrary.reject(error); + return Promise.reject(error); } changeStream.attemptingResume = false; @@ -558,7 +561,7 @@ function processNewChange(args) { if (eventEmitter) return changeStream.emit('error', noResumeTokenError); if (typeof callback === 'function') return callback(noResumeTokenError, null); - return changeStream.promiseLibrary.reject(noResumeTokenError); + return Promise.reject(noResumeTokenError); } // cache the resume token @@ -571,7 +574,7 @@ function processNewChange(args) { // Return the change if (eventEmitter) return changeStream.emit('change', change); if (typeof callback === 'function') return callback(error, change); - return changeStream.promiseLibrary.resolve(change); + return Promise.resolve(change); } /** diff --git a/lib/collection.js b/lib/collection.js index 6fca82770b7..348ca9db286 100644 --- a/lib/collection.js +++ b/lib/collection.js @@ -1,5 +1,6 @@ 'use strict'; +const PromiseProvider = require('./promise_provider'); const deprecate = require('util').deprecate; const deprecateOptions = require('./utils').deprecateOptions; const checkCollectionName = require('./utils').checkCollectionName; @@ -128,7 +129,7 @@ function Collection(db, topology, dbName, name, pkFactory, options) { const namespace = new MongoDBNamespace(dbName, name); // Get the promiseLibrary - const promiseLibrary = options.promiseLibrary || Promise; + const promiseLibrary = PromiseProvider.get(options, db, topology); // Set custom primary key factory if provided pkFactory = pkFactory == null ? ObjectID : pkFactory; @@ -443,7 +444,7 @@ Collection.prototype.find = deprecateOptions( newOptions.db = this.s.db; // Add the promise library - newOptions.promiseLibrary = this.s.promiseLibrary; + newOptions.promiseLibrary = PromiseProvider.get(options, this); // Set raw if available at collection level if (newOptions.raw == null && typeof this.s.raw === 'boolean') newOptions.raw = this.s.raw; @@ -748,13 +749,14 @@ Collection.prototype.insert = deprecate(function(docs, options, callback) { * @return {Promise} returns Promise if no callback passed */ Collection.prototype.updateOne = function(filter, update, options, callback) { + const Promise = PromiseProvider.get(this); if (typeof options === 'function') (callback = options), (options = {}); options = options || {}; const err = checkForAtomicOperators(update); if (err) { if (typeof callback === 'function') return callback(err); - return this.s.promiseLibrary.reject(err); + return Promise.reject(err); } options = Object.assign({}, options); @@ -827,13 +829,14 @@ Collection.prototype.replaceOne = function(filter, doc, options, callback) { * @return {Promise} returns Promise if no callback passed */ Collection.prototype.updateMany = function(filter, update, options, callback) { + const Promise = PromiseProvider.get(this); if (typeof options === 'function') (callback = options), (options = {}); options = options || {}; const err = checkForAtomicOperators(update); if (err) { if (typeof callback === 'function') return callback(err); - return this.s.promiseLibrary.reject(err); + return Promise.reject(err); } options = Object.assign({}, options); @@ -1754,6 +1757,7 @@ Collection.prototype.findOneAndReplace = function(filter, replacement, options, * @return {Promise} returns Promise if no callback passed */ Collection.prototype.findOneAndUpdate = function(filter, update, options, callback) { + const Promise = PromiseProvider.get(this); if (typeof options === 'function') (callback = options), (options = {}); options = options || {}; @@ -1766,7 +1770,7 @@ Collection.prototype.findOneAndUpdate = function(filter, update, options, callba const err = checkForAtomicOperators(update); if (err) { if (typeof callback === 'function') return callback(err); - return this.s.promiseLibrary.reject(err); + return Promise.reject(err); } const findOneAndUpdateOperation = new FindOneAndUpdateOperation(this, filter, update, options); @@ -1990,7 +1994,7 @@ Collection.prototype.parallelCollectionScan = deprecate(function(options, callba options.readPreference = resolveReadPreference(this, options); // Add a promiseLibrary - options.promiseLibrary = this.s.promiseLibrary; + options.promiseLibrary = PromiseProvider.get(this); if (options.session) { options.session = undefined; @@ -2171,7 +2175,7 @@ Collection.prototype.initializeUnorderedBulkOp = function(options) { options.ignoreUndefined = this.s.options.ignoreUndefined; } - options.promiseLibrary = this.s.promiseLibrary; + options.promiseLibrary = PromiseProvider.get(this); return unordered(this.s.topology, this, options); }; @@ -2194,7 +2198,7 @@ Collection.prototype.initializeOrderedBulkOp = function(options) { if (options.ignoreUndefined == null) { options.ignoreUndefined = this.s.options.ignoreUndefined; } - options.promiseLibrary = this.s.promiseLibrary; + options.promiseLibrary = PromiseProvider.get(this); return ordered(this.s.topology, this, options); }; diff --git a/lib/core/sdam/topology.js b/lib/core/sdam/topology.js index 8b6d65c2724..e4fded9e90d 100644 --- a/lib/core/sdam/topology.js +++ b/lib/core/sdam/topology.js @@ -1,4 +1,6 @@ 'use strict'; + +const PromiseProvider = require('../../promise_provider'); const Denque = require('denque'); const EventEmitter = require('events'); const ServerDescription = require('./server_description').ServerDescription; @@ -193,7 +195,7 @@ class Topology extends EventEmitter { // Active client sessions sessions: new Set(), // Promise library - promiseLibrary: options.promiseLibrary || Promise, + promiseLibrary: PromiseProvider.get(options), credentials: options.credentials, clusterTime: null, diff --git a/lib/core/sessions.js b/lib/core/sessions.js index e215ade8c07..0374ee249bb 100644 --- a/lib/core/sessions.js +++ b/lib/core/sessions.js @@ -1,5 +1,6 @@ 'use strict'; +const PromiseProvider = require('../promise_provider'); const retrieveBSON = require('./connection/utils').retrieveBSON; const EventEmitter = require('events'); const BSON = retrieveBSON(); @@ -216,6 +217,7 @@ class ClientSession extends EventEmitter { * @return {Promise} A promise is returned if no callback is provided */ commitTransaction(callback) { + const Promise = PromiseProvider.get(this); if (typeof callback === 'function') { endTransaction(this, 'commitTransaction', callback); return; @@ -235,6 +237,7 @@ class ClientSession extends EventEmitter { * @return {Promise} A promise is returned if no callback is provided */ abortTransaction(callback) { + const Promise = PromiseProvider.get(this); if (typeof callback === 'function') { endTransaction(this, 'abortTransaction', callback); return; @@ -342,6 +345,7 @@ function userExplicitlyEndedTransaction(session) { } function attemptTransaction(session, startTime, fn, options) { + const Promise = PromiseProvider.get(session); session.startTransaction(options); let promise; diff --git a/lib/cursor.js b/lib/cursor.js index 5cb1df89934..66b322cc2ba 100644 --- a/lib/cursor.js +++ b/lib/cursor.js @@ -1,5 +1,6 @@ 'use strict'; +const PromiseProvider = require('./promise_provider'); const Transform = require('stream').Transform; const PassThrough = require('stream').PassThrough; const deprecate = require('util').deprecate; @@ -111,7 +112,7 @@ class Cursor extends CoreCursor { const currentNumberOfRetries = numberOfRetries; // Get the promiseLibrary - const promiseLibrary = options.promiseLibrary || Promise; + const promiseLibrary = PromiseProvider.get(options, topology); // Internal cursor state this.s = { @@ -727,6 +728,7 @@ class Cursor extends CoreCursor { * @return {Promise} if no callback supplied */ forEach(iterator, callback) { + const Promise = PromiseProvider.get(this); // Rewind cursor state this.rewind(); @@ -751,7 +753,7 @@ class Cursor extends CoreCursor { } }); } else { - return new this.s.promiseLibrary((fulfill, reject) => { + return new Promise((fulfill, reject) => { each(this, (err, doc) => { if (err) { reject(err); @@ -910,6 +912,7 @@ class Cursor extends CoreCursor { * @return {Promise} returns Promise if no callback passed */ close(options, callback) { + const Promise = PromiseProvider.get(this); if (typeof options === 'function') (callback = options), (options = {}); options = Object.assign({}, { skipKillCursors: false }, options); @@ -929,9 +932,7 @@ class Cursor extends CoreCursor { } // Return a Promise - return new this.s.promiseLibrary(resolve => { - resolve(); - }); + return Promise.resolve(); }; if (this.cursorState.session) { @@ -939,7 +940,7 @@ class Cursor extends CoreCursor { return this._endSession(() => completeClose()); } - return new this.s.promiseLibrary(resolve => { + return new Promise(resolve => { this._endSession(() => completeClose().then(resolve)); }); } diff --git a/lib/db.js b/lib/db.js index e28d25a90f8..bce26443ac4 100644 --- a/lib/db.js +++ b/lib/db.js @@ -1,5 +1,6 @@ 'use strict'; +const PromiseProvider = require('./promise_provider'); const EventEmitter = require('events').EventEmitter; const inherits = require('util').inherits; const getSingleProperty = require('./utils').getSingleProperty; @@ -143,7 +144,7 @@ function Db(databaseName, topology, options) { EventEmitter.call(this); // Get the promiseLibrary - const promiseLibrary = options.promiseLibrary || Promise; + const promiseLibrary = PromiseProvider.get(options, topology); // Filter the options options = filterOptions(options, legalOptionNames); @@ -355,7 +356,7 @@ Db.prototype.aggregate = function(pipeline, options, callback) { Db.prototype.admin = function() { const Admin = require('./admin'); - return new Admin(this, this.s.topology, this.s.promiseLibrary); + return new Admin(this, this.s.topology); }; /** @@ -410,7 +411,7 @@ Db.prototype.collection = function(name, options, callback) { options = Object.assign({}, options); // Set the promise library - options.promiseLibrary = this.s.promiseLibrary; + options.promiseLibrary = PromiseProvider.get(options, this); // If we have not set a collection level readConcern set the db level one options.readConcern = options.readConcern @@ -519,7 +520,7 @@ Db.prototype.createCollection = deprecateOptions( function(name, options, callback) { if (typeof options === 'function') (callback = options), (options = {}); options = options || {}; - options.promiseLibrary = options.promiseLibrary || this.s.promiseLibrary; + options.promiseLibrary = PromiseProvider.get(options, this); options.readConcern = options.readConcern ? new ReadConcern(options.readConcern.level) : this.readConcern; diff --git a/lib/gridfs-stream/index.js b/lib/gridfs-stream/index.js index 65098395187..410f94b1be3 100644 --- a/lib/gridfs-stream/index.js +++ b/lib/gridfs-stream/index.js @@ -1,5 +1,6 @@ 'use strict'; +const PromiseProvider = require('../promise_provider'); var Emitter = require('events').EventEmitter; var GridFSBucketReadStream = require('./download'); var GridFSBucketWriteStream = require('./upload'); @@ -51,7 +52,7 @@ function GridFSBucket(db, options) { _filesCollection: db.collection(options.bucketName + '.files'), checkedIndexes: false, calledOpenUploadStream: false, - promiseLibrary: db.s.promiseLibrary || Promise + promiseLibrary: PromiseProvider.get(db) }; } diff --git a/lib/gridfs/grid_store.js b/lib/gridfs/grid_store.js index 9d9ff25f3af..a1a6dbac3c5 100644 --- a/lib/gridfs/grid_store.js +++ b/lib/gridfs/grid_store.js @@ -35,6 +35,8 @@ * }); * }); */ + +const PromiseProvider = require('../promise_provider'); const Chunk = require('./chunk'); const ObjectID = require('../core').BSON.ObjectID; const ReadPreference = require('../core').ReadPreference; @@ -140,11 +142,8 @@ var GridStore = function GridStore(db, id, filename, mode, options) { this.internalChunkSize = this.options['chunkSize'] == null ? Chunk.DEFAULT_CHUNK_SIZE : this.options['chunkSize']; - // Get the promiseLibrary - var promiseLibrary = this.options.promiseLibrary || Promise; - // Set the promiseLibrary - this.promiseLibrary = promiseLibrary; + this.promiseLibrary = PromiseProvider.get(options, db); Object.defineProperty(this, 'chunkSize', { enumerable: true, diff --git a/lib/mongo_client.js b/lib/mongo_client.js index 102f4ff8206..7a2e31e38c1 100644 --- a/lib/mongo_client.js +++ b/lib/mongo_client.js @@ -1,5 +1,6 @@ 'use strict'; +const PromiseProvider = require('./promise_provider'); const ChangeStream = require('./change_stream'); const Db = require('./db'); const EventEmitter = require('events').EventEmitter; @@ -158,7 +159,7 @@ function MongoClient(url, options) { this.s = { url: url, options: options || {}, - promiseLibrary: (options && options.promiseLibrary) || Promise, + promiseLibrary: PromiseProvider.get(options), dbCache: new Map(), sessions: new Set(), writeConcern: WriteConcern.fromOptions(options), @@ -300,7 +301,7 @@ MongoClient.prototype.db = function(dbName, options) { } // Add promiseLibrary - finalOptions.promiseLibrary = this.s.promiseLibrary; + finalOptions.promiseLibrary = PromiseProvider.get(options, this); // If no topology throw an error message if (!this.topology) { @@ -453,6 +454,7 @@ MongoClient.prototype.startSession = function(options) { * @return {Promise} */ MongoClient.prototype.withSession = function(options, operation) { + const Promise = PromiseProvider.get(this); if (typeof options === 'function') (operation = options), (options = undefined); const session = this.startSession(options); diff --git a/lib/operations/connect.js b/lib/operations/connect.js index 12035fbfbba..791d47c2b03 100644 --- a/lib/operations/connect.js +++ b/lib/operations/connect.js @@ -1,5 +1,6 @@ 'use strict'; +const PromiseProvider = require('../promise_provider'); const deprecate = require('util').deprecate; const Logger = require('../core').Logger; const MongoCredentials = require('../core').MongoCredentials; @@ -403,7 +404,7 @@ function createListener(mongoClient, event) { function createServer(mongoClient, options, callback) { // Pass in the promise library - options.promiseLibrary = mongoClient.s.promiseLibrary; + options.promiseLibrary = PromiseProvider.get(mongoClient); // Set default options const servers = translateOptions(options); @@ -473,7 +474,7 @@ function registerDeprecatedEventNotifiers(client) { function createTopology(mongoClient, topologyType, options, callback) { // Pass in the promise library - options.promiseLibrary = mongoClient.s.promiseLibrary; + options.promiseLibrary = PromiseProvider.get(options, mongoClient); const translationOptions = {}; if (topologyType === 'unified') translationOptions.createServers = false; diff --git a/lib/promise_provider.js b/lib/promise_provider.js new file mode 100644 index 00000000000..ec8740d6bce --- /dev/null +++ b/lib/promise_provider.js @@ -0,0 +1,45 @@ +'use strict'; + +const kPromise = Symbol('promise'); + +const store = { + [kPromise]: global.Promise +}; + +/** global promise store allowing user-provided promises */ +class PromiseProvider { + /** validates the passed in promise library */ + static validate(lib) { + if (typeof lib !== 'function') throw new Error(`Promise must be a function, got ${lib}`); + return lib; + } + + /** sets the promise library */ + static set(lib) { + store[kPromise] = PromiseProvider.validate(lib); + } + + /** get the stored promise library, or resolves passed in */ + static get() { + const result = Object.keys(arguments).reduce((acq, k) => { + const parent = arguments[k]; + if (acq) return acq; + if (parent && parent.promiseLibrary) { + return PromiseProvider.validate(parent.promiseLibrary); + } + if (parent && parent.options && parent.options.promiseLibrary) { + return PromiseProvider.validate(parent.options.promiseLibrary); + } + if (parent && parent.s && parent.s.promiseLibrary) { + return PromiseProvider.validate(parent.s.promiseLibrary); + } + if (parent && parent.clientOptions && parent.clientOptions.promiseLibrary) { + return PromiseProvider.validate(parent.clientOptions.promiseLibrary); + } + return null; + }, null); + return result || store[kPromise]; + } +} + +module.exports = PromiseProvider; diff --git a/lib/topologies/mongos.js b/lib/topologies/mongos.js index 10e66d2151b..45940f0d6b2 100644 --- a/lib/topologies/mongos.js +++ b/lib/topologies/mongos.js @@ -1,5 +1,6 @@ 'use strict'; +const PromiseProvider = require('../promise_provider'); const TopologyBase = require('./topology_base').TopologyBase; const MongoError = require('../core').MongoError; const CMongos = require('../core').Mongos; @@ -189,7 +190,7 @@ class Mongos extends TopologyBase { // Active client sessions sessions: new Set(), // Promise library - promiseLibrary: options.promiseLibrary || Promise + promiseLibrary: PromiseProvider.get(options) }; } diff --git a/lib/topologies/replset.js b/lib/topologies/replset.js index 69df26d19e0..5f3a188220f 100644 --- a/lib/topologies/replset.js +++ b/lib/topologies/replset.js @@ -1,5 +1,6 @@ 'use strict'; +const PromiseProvider = require('../promise_provider'); const Server = require('./server'); const Cursor = require('../cursor'); const MongoError = require('../core').MongoError; @@ -205,7 +206,7 @@ class ReplSet extends TopologyBase { // Active client sessions sessions: new Set(), // Promise library - promiseLibrary: options.promiseLibrary || Promise + promiseLibrary: PromiseProvider.get(options) }; // Debug diff --git a/lib/topologies/server.js b/lib/topologies/server.js index 3079cb9953e..89e8f537489 100644 --- a/lib/topologies/server.js +++ b/lib/topologies/server.js @@ -1,5 +1,6 @@ 'use strict'; +const PromiseProvider = require('../promise_provider'); const CServer = require('../core').Server; const Cursor = require('../cursor'); const TopologyBase = require('./topology_base').TopologyBase; @@ -114,7 +115,7 @@ class Server extends TopologyBase { options = filterOptions(options, legalOptionNames); // Promise library - const promiseLibrary = options.promiseLibrary; + const promiseLibrary = PromiseProvider.get(options); // Stored options var storeOptions = { @@ -197,7 +198,7 @@ class Server extends TopologyBase { // Active client sessions sessions: new Set(), // Promise library - promiseLibrary: promiseLibrary || Promise + promiseLibrary: promiseLibrary }; } diff --git a/lib/utils.js b/lib/utils.js index 2ce8158bd59..63a1ff0c64a 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,4 +1,6 @@ 'use strict'; + +const PromiseProvider = require('./promise_provider'); const MongoError = require('./core/error').MongoError; const ReadPreference = require('./core/topologies/read_preference'); const WriteConcern = require('./write_concern'); @@ -372,7 +374,7 @@ const executeLegacyOperation = (topology, operation, args, options) => { } options = options || {}; - const Promise = topology.s.promiseLibrary; + const Promise = PromiseProvider.get(topology); let callback = args[args.length - 1]; // The driver sessions spec mandates that we implicitly create sessions for operations @@ -705,11 +707,11 @@ function* makeCounter(seed) { * @returns {Promise|void} Returns nothing if a callback is supplied, else returns a Promise. */ function maybePromise(parent, callback, fn) { - const PromiseLibrary = (parent && parent.s && parent.s.promiseLibrary) || Promise; + const Promise = PromiseProvider.get(parent); let result; if (typeof callback !== 'function') { - result = new PromiseLibrary((resolve, reject) => { + result = new Promise((resolve, reject) => { callback = (err, res) => { if (err) return reject(err); resolve(res); diff --git a/package-lock.json b/package-lock.json index babc1268104..8200ee398e2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1252,6 +1252,12 @@ "jest-docblock": "^21.0.0" } }, + "eslint-plugin-promise": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz", + "integrity": "sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw==", + "dev": true + }, "eslint-scope": { "version": "3.7.3", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.3.tgz", diff --git a/package.json b/package.json index c63d13b831c..730655b6bd8 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "coveralls": "^2.11.6", "eslint": "^4.5.0", "eslint-plugin-prettier": "^2.2.0", + "eslint-plugin-promise": "^4.2.1", "istanbul": "^0.4.5", "jsdoc": "3.5.5", "lodash.camelcase": "^4.3.0", diff --git a/test/benchmarks/driverBench/index.js b/test/benchmarks/driverBench/index.js index be3ccd1335c..4cef0dc52c0 100644 --- a/test/benchmarks/driverBench/index.js +++ b/test/benchmarks/driverBench/index.js @@ -1,5 +1,6 @@ 'use strict'; +const Promise = require('../../../lib/promise_provider').get(); const MongoBench = require('../mongoBench'); const Runner = MongoBench.Runner; diff --git a/test/benchmarks/mongoBench/runner.js b/test/benchmarks/mongoBench/runner.js index 5e06ec5f894..4a90492807a 100644 --- a/test/benchmarks/mongoBench/runner.js +++ b/test/benchmarks/mongoBench/runner.js @@ -1,5 +1,6 @@ 'use strict'; +const Promise = require('../../../lib/promise_provider').get(); const CONSTANTS = require('./constants'); function hrtimeToSeconds(hrtime) { diff --git a/test/benchmarks/mongoBench/util.js b/test/benchmarks/mongoBench/util.js index 7ed14672bdf..685aa923f92 100644 --- a/test/benchmarks/mongoBench/util.js +++ b/test/benchmarks/mongoBench/util.js @@ -1,5 +1,7 @@ 'use strict'; +const Promise = require('../../../lib/promise_provider').get(); + function asyncChain(_chain) { // Make a copy of the chain so people can't mutate it :) const chain = [].concat(_chain); diff --git a/test/examples/change_streams.js b/test/examples/change_streams.js index df889331ace..862a5e238fe 100644 --- a/test/examples/change_streams.js +++ b/test/examples/change_streams.js @@ -1,6 +1,7 @@ /* eslint no-unused-vars: 0 */ 'use strict'; +const Promise = require('../../lib/promise_provider').get(); const setupDatabase = require('../functional/shared').setupDatabase; const expect = require('chai').expect; diff --git a/test/functional/aggregation.test.js b/test/functional/aggregation.test.js index 1ab06ad801c..4c26bfb12b9 100644 --- a/test/functional/aggregation.test.js +++ b/test/functional/aggregation.test.js @@ -1,4 +1,6 @@ 'use strict'; + +const Promise = require('../../lib/promise_provider').get(); var expect = require('chai').expect; describe('Aggregation', function() { diff --git a/test/functional/apm.test.js b/test/functional/apm.test.js index 3f229b751af..a8cd4089a2b 100644 --- a/test/functional/apm.test.js +++ b/test/functional/apm.test.js @@ -1,5 +1,6 @@ 'use strict'; +const Promise = require('../../lib/promise_provider').get(); const instrument = require('../..').instrument; const shared = require('./shared'); const setupDatabase = shared.setupDatabase; diff --git a/test/functional/byo_promises.test.js b/test/functional/byo_promises.test.js index 70cb7065906..55c9cc13a1f 100644 --- a/test/functional/byo_promises.test.js +++ b/test/functional/byo_promises.test.js @@ -1,5 +1,6 @@ 'use strict'; +const Promise = require('../../lib/promise_provider').get(); const maybePromise = require('./../../lib/utils').maybePromise; var expect = require('chai').expect; diff --git a/test/functional/change_stream.test.js b/test/functional/change_stream.test.js index ee0231d49ea..d685fb815e4 100644 --- a/test/functional/change_stream.test.js +++ b/test/functional/change_stream.test.js @@ -1,4 +1,6 @@ 'use strict'; + +const Promise = require('../../lib/promise_provider').get(); var assert = require('assert'); var Transform = require('stream').Transform; const MongoError = require('../../lib/core').MongoError; diff --git a/test/functional/change_stream_spec.test.js b/test/functional/change_stream_spec.test.js index 7fe16f14f94..01ba86df816 100644 --- a/test/functional/change_stream_spec.test.js +++ b/test/functional/change_stream_spec.test.js @@ -1,5 +1,6 @@ 'use strict'; +const Promise = require('../../lib/promise_provider').get(); const chai = require('chai'); const loadSpecTests = require('../spec').loadSpecTests; const camelCase = require('lodash.camelcase'); diff --git a/test/functional/client_side_encryption/corpus.test.js b/test/functional/client_side_encryption/corpus.test.js index 46e65597c83..02e0ee55cfe 100644 --- a/test/functional/client_side_encryption/corpus.test.js +++ b/test/functional/client_side_encryption/corpus.test.js @@ -2,6 +2,7 @@ // The corpus test exhaustively enumerates all ways to encrypt all BSON value types. Note, the test data includes BSON binary subtype 4 (or standard UUID), which MUST be decoded and encoded as subtype 4. Run the test as follows. +const Promise = require('../../../lib/promise_provider').get(); const fs = require('fs'); const path = require('path'); const EJSON = require('mongodb-extjson'); diff --git a/test/functional/client_side_encryption/driver.test.js b/test/functional/client_side_encryption/driver.test.js index ff36cd1a415..d2a304be00e 100644 --- a/test/functional/client_side_encryption/driver.test.js +++ b/test/functional/client_side_encryption/driver.test.js @@ -1,5 +1,6 @@ 'use strict'; +const Promise = require('../../../lib/promise_provider').get(); const crypto = require('crypto'); const BSON = require('bson'); const bson = new BSON(); diff --git a/test/functional/client_side_encryption/prose.test.js b/test/functional/client_side_encryption/prose.test.js index 5c5abfd67f9..6efab269fa6 100644 --- a/test/functional/client_side_encryption/prose.test.js +++ b/test/functional/client_side_encryption/prose.test.js @@ -1,5 +1,6 @@ 'use strict'; +const Promise = require('../../../lib/promise_provider').get(); const chai = require('chai'); const expect = chai.expect; chai.use(require('chai-subset')); diff --git a/test/functional/collations.test.js b/test/functional/collations.test.js index ea177a44490..c152a84e95c 100644 --- a/test/functional/collations.test.js +++ b/test/functional/collations.test.js @@ -1,4 +1,6 @@ 'use strict'; + +const Promise = require('../../lib/promise_provider').get(); const setupDatabase = require('./shared').setupDatabase; const mock = require('mongodb-mock-server'); const expect = require('chai').expect; diff --git a/test/functional/collection.test.js b/test/functional/collection.test.js index a149757e88e..d3bf5bd900c 100644 --- a/test/functional/collection.test.js +++ b/test/functional/collection.test.js @@ -1,4 +1,6 @@ 'use strict'; + +const Promise = require('../../lib/promise_provider').get(); const setupDatabase = require('./shared').setupDatabase; const chai = require('chai'); const expect = chai.expect; diff --git a/test/functional/connections_stepdown.test.js b/test/functional/connections_stepdown.test.js index d63ecada687..030b941e648 100644 --- a/test/functional/connections_stepdown.test.js +++ b/test/functional/connections_stepdown.test.js @@ -1,5 +1,6 @@ 'use strict'; +const Promise = require('../../lib/promise_provider').get(); const chai = require('chai'); const expect = chai.expect; diff --git a/test/functional/core/rs_mocks/monitoring.test.js b/test/functional/core/rs_mocks/monitoring.test.js index 2afec0214d3..16a635d3266 100644 --- a/test/functional/core/rs_mocks/monitoring.test.js +++ b/test/functional/core/rs_mocks/monitoring.test.js @@ -1,4 +1,6 @@ 'use strict'; + +const Promise = require('../../../../lib/promise_provider').get(); const expect = require('chai').expect; const co = require('co'); const mock = require('mongodb-mock-server'); diff --git a/test/functional/core/shared.js b/test/functional/core/shared.js index 915d9b8f2d7..2f4526aaea4 100644 --- a/test/functional/core/shared.js +++ b/test/functional/core/shared.js @@ -1,4 +1,6 @@ 'use strict'; + +const Promise = require('../../../lib/promise_provider').get(); const EventEmitter = require('events'); const Pool = require('../../../lib/core/connection/pool'); const f = require('util').format; diff --git a/test/functional/crud_spec.test.js b/test/functional/crud_spec.test.js index 55c4cf9ee39..1048421eea1 100644 --- a/test/functional/crud_spec.test.js +++ b/test/functional/crud_spec.test.js @@ -1,5 +1,6 @@ 'use strict'; +const Promise = require('../../lib/promise_provider').get(); const fs = require('fs'); const path = require('path'); const chai = require('chai'); diff --git a/test/functional/cursor.test.js b/test/functional/cursor.test.js index 64a07f2ab3b..3582f6bacb1 100644 --- a/test/functional/cursor.test.js +++ b/test/functional/cursor.test.js @@ -1,4 +1,6 @@ 'use strict'; + +const Promise = require('../../lib/promise_provider').get(); const test = require('./shared').assert; const setupDatabase = require('./shared').setupDatabase; const fs = require('fs'); diff --git a/test/functional/operation_generators_example.test.js b/test/functional/operation_generators_example.test.js index 2b4fbf8aae5..0ef3daac7b5 100644 --- a/test/functional/operation_generators_example.test.js +++ b/test/functional/operation_generators_example.test.js @@ -1,4 +1,6 @@ 'use strict'; + +const Promise = require('../../lib/promise_provider').get(); var test = require('./shared').assert; var setupDatabase = require('./shared').setupDatabase; var Buffer = require('safe-buffer').Buffer; diff --git a/test/functional/operation_promises_example.test.js b/test/functional/operation_promises_example.test.js index 1be1268c9ab..7f2a13de5d8 100644 --- a/test/functional/operation_promises_example.test.js +++ b/test/functional/operation_promises_example.test.js @@ -1,4 +1,6 @@ 'use strict'; + +const Promise = require('../../lib/promise_provider').get(); var fs = require('fs'); var f = require('util').format; var test = require('./shared').assert; diff --git a/test/functional/promises_db.test.js b/test/functional/promises_db.test.js index d3cdf4c0946..81f267c5f5e 100644 --- a/test/functional/promises_db.test.js +++ b/test/functional/promises_db.test.js @@ -1,4 +1,6 @@ 'use strict'; + +const Promise = require('../../lib/promise_provider').get(); var test = require('./shared').assert; var setupDatabase = require('./shared').setupDatabase; var f = require('util').format; diff --git a/test/functional/replicaset_mock.test.js b/test/functional/replicaset_mock.test.js index ca063a55cb0..a4834402894 100644 --- a/test/functional/replicaset_mock.test.js +++ b/test/functional/replicaset_mock.test.js @@ -1,4 +1,6 @@ 'use strict'; + +const Promise = require('../../lib/promise_provider').get(); var expect = require('chai').expect, mock = require('mongodb-mock-server'), ObjectId = require('bson').ObjectId; diff --git a/test/functional/retryable_writes.test.js b/test/functional/retryable_writes.test.js index 46c91d8f126..5e08bf0fa4c 100644 --- a/test/functional/retryable_writes.test.js +++ b/test/functional/retryable_writes.test.js @@ -1,5 +1,6 @@ 'use strict'; +const Promise = require('../../lib/promise_provider').get(); const expect = require('chai').expect; const loadSpecTests = require('../spec').loadSpecTests; const parseRunOn = require('../functional/spec-runner').parseRunOn; diff --git a/test/functional/saslprep.test.js b/test/functional/saslprep.test.js index 1bb21c86da9..d99bd050d4b 100644 --- a/test/functional/saslprep.test.js +++ b/test/functional/saslprep.test.js @@ -1,5 +1,6 @@ 'use strict'; +const Promise = require('../../lib/promise_provider').get(); const setupDatabase = require('./shared').setupDatabase; const withClient = require('./shared').withClient; diff --git a/test/functional/scram_sha_256.test.js b/test/functional/scram_sha_256.test.js index 8ca0838aa28..236aa0cfa1c 100644 --- a/test/functional/scram_sha_256.test.js +++ b/test/functional/scram_sha_256.test.js @@ -1,5 +1,6 @@ 'use strict'; +const Promise = require('../../lib/promise_provider').get(); const expect = require('chai').expect; const sinon = require('sinon'); const ScramSHA256 = require('../../lib/core').ScramSHA256; diff --git a/test/functional/sessions.test.js b/test/functional/sessions.test.js index c79510001d2..03ab7098a8c 100644 --- a/test/functional/sessions.test.js +++ b/test/functional/sessions.test.js @@ -1,4 +1,6 @@ 'use strict'; + +const Promise = require('../../lib/promise_provider').get(); const expect = require('chai').expect; const setupDatabase = require('./shared').setupDatabase; const TestRunnerContext = require('./spec-runner').TestRunnerContext; diff --git a/test/functional/shared.js b/test/functional/shared.js index 65023355140..a318c68089f 100644 --- a/test/functional/shared.js +++ b/test/functional/shared.js @@ -1,5 +1,6 @@ 'use strict'; +const Promise = require('../../lib/promise_provider').get(); const MongoClient = require('../../').MongoClient; const expect = require('chai').expect; diff --git a/test/functional/spec-runner/context.js b/test/functional/spec-runner/context.js index 04a8faac0d8..9c8ce24df16 100644 --- a/test/functional/spec-runner/context.js +++ b/test/functional/spec-runner/context.js @@ -1,4 +1,6 @@ 'use strict'; + +const Promise = require('../../../lib/promise_provider').get(); const expect = require('chai').expect; const resolveConnectionString = require('./utils').resolveConnectionString; diff --git a/test/functional/spec-runner/index.js b/test/functional/spec-runner/index.js index 4ef026eac9d..5807e9ac0b8 100644 --- a/test/functional/spec-runner/index.js +++ b/test/functional/spec-runner/index.js @@ -1,5 +1,6 @@ 'use strict'; +const Promise = require('../../../lib/promise_provider').get(); const path = require('path'); const fs = require('fs'); const chai = require('chai'); diff --git a/test/functional/transactions.test.js b/test/functional/transactions.test.js index d705819218d..e831279f5c6 100644 --- a/test/functional/transactions.test.js +++ b/test/functional/transactions.test.js @@ -1,5 +1,6 @@ 'use strict'; +const Promise = require('../../lib/promise_provider').get(); const chai = require('chai'); const expect = chai.expect; const core = require('../../lib/core'); diff --git a/test/tools/atlas_connectivity_tests.js b/test/tools/atlas_connectivity_tests.js index e6947bbdbc6..aede7931460 100644 --- a/test/tools/atlas_connectivity_tests.js +++ b/test/tools/atlas_connectivity_tests.js @@ -1,5 +1,6 @@ 'use strict'; +const Promise = require('../../lib/promise_provider').get(); const MongoClient = require('../..').MongoClient; const CONFIGS = ['ATLAS_REPL', 'ATLAS_SHRD', 'ATLAS_FREE', 'ATLAS_TLS11', 'ATLAS_TLS12'].map( diff --git a/test/tools/runner/plugins/deferred.js b/test/tools/runner/plugins/deferred.js index 990b0e30308..ede5c4e2f56 100644 --- a/test/tools/runner/plugins/deferred.js +++ b/test/tools/runner/plugins/deferred.js @@ -1,4 +1,6 @@ 'use strict'; + +const Promise = require('../../../../lib/promise_provider').get(); const kDeferred = Symbol('deferred'); (mocha => { diff --git a/test/unit/cmap/connection_pool.test.js b/test/unit/cmap/connection_pool.test.js index 16fee35ee0e..33d4c20668f 100644 --- a/test/unit/cmap/connection_pool.test.js +++ b/test/unit/cmap/connection_pool.test.js @@ -1,5 +1,6 @@ 'use strict'; +const Promise = require('../../../lib/promise_provider').get(); require('util.promisify/shim')(); const util = require('util'); const loadSpecTests = require('../../spec').loadSpecTests; diff --git a/test/unit/core/common.js b/test/unit/core/common.js index 4fa8def73c2..19c2bd84071 100644 --- a/test/unit/core/common.js +++ b/test/unit/core/common.js @@ -1,5 +1,6 @@ 'use strict'; +const Promise = require('../../../lib/promise_provider').get(); const mock = require('mongodb-mock-server'); const ObjectId = require('bson').ObjectId; const Timestamp = require('bson').Timestamp; diff --git a/test/unit/core/mongos/reconnect.test.js b/test/unit/core/mongos/reconnect.test.js index 6af2f8a76e7..5b63be3459d 100644 --- a/test/unit/core/mongos/reconnect.test.js +++ b/test/unit/core/mongos/reconnect.test.js @@ -1,5 +1,6 @@ 'use strict'; +const Promise = require('../../../../lib/promise_provider').get(); const Mongos = require('../../../../lib/core/topologies/mongos'); const expect = require('chai').expect; const mock = require('mongodb-mock-server'); diff --git a/test/unit/core/sessions.test.js b/test/unit/core/sessions.test.js index 81d7af1b263..2d8ba296a33 100644 --- a/test/unit/core/sessions.test.js +++ b/test/unit/core/sessions.test.js @@ -1,5 +1,6 @@ 'use strict'; +const Promise = require('../../../lib/promise_provider').get(); const mock = require('mongodb-mock-server'); const expect = require('chai').expect; const genClusterTime = require('./common').genClusterTime; diff --git a/test/unit/db.test.js b/test/unit/db.test.js index aef7e4b4c44..1cef260d0e0 100644 --- a/test/unit/db.test.js +++ b/test/unit/db.test.js @@ -1,5 +1,6 @@ 'use strict'; +const Promise = require('../../lib/promise_provider').get(); const EventEmitter = require('events'); const expect = require('chai').expect; const sinon = require('sinon'); diff --git a/test/unit/execute_legacy_operation.test.js b/test/unit/execute_legacy_operation.test.js index bcceb2f0601..ba3cec46dd1 100644 --- a/test/unit/execute_legacy_operation.test.js +++ b/test/unit/execute_legacy_operation.test.js @@ -1,5 +1,6 @@ 'use strict'; +const Promise = require('../../lib/promise_provider').get(); const expect = require('chai').expect; const executeLegacyOperation = require('../../lib/utils').executeLegacyOperation; diff --git a/test/unit/promise_provider.test.js b/test/unit/promise_provider.test.js new file mode 100644 index 00000000000..50c87682800 --- /dev/null +++ b/test/unit/promise_provider.test.js @@ -0,0 +1,48 @@ +'use strict'; +const expect = require('chai').expect; + +const PromiseProvider = require('../../lib/promise_provider'); + +const symbols = { + withOptions: Symbol('withOptions'), + withClientOptions: Symbol('withClientOptions'), + withS: Symbol('withS') +}; + +const candidates = { + withOptions: { options: { promiseLibrary: () => symbols.withOptions } }, + withClientOptions: { clientOptions: { promiseLibrary: () => symbols.withClientOptions } }, + withS: { s: { promiseLibrary: () => symbols.withS } } +}; + +describe('PromiseProvider', () => { + it('should pass correct option along', () => { + const withOptions = PromiseProvider.get(candidates.withOptions); + expect(withOptions()).to.equal(symbols.withOptions); + + const withClientOptions = PromiseProvider.get(candidates.withClientOptions); + expect(withClientOptions()).to.equal(symbols.withClientOptions); + + const withS = PromiseProvider.get(candidates.withS); + expect(withS()).to.equal(symbols.withS); + + // test non-viable option first, still retrieves second option + const preWithOptions = PromiseProvider.get({}, candidates.withOptions); + expect(preWithOptions()).to.equal(symbols.withOptions); + + const preWithClientOptions = PromiseProvider.get({}, candidates.withClientOptions); + expect(preWithClientOptions()).to.equal(symbols.withClientOptions); + + const preWithS = PromiseProvider.get({}, candidates.withS); + expect(preWithS()).to.equal(symbols.withS); + + // non-viable, with two viable, shows gets first viable + const getsPremier = PromiseProvider.get({}, candidates.withOptions, candidates.withS); + expect(getsPremier()).to.equal(symbols.withOptions); + + // demonstrates default is retrieved + const state = PromiseProvider.get(); + const empty = PromiseProvider.get({}, null); + expect(empty).to.equal(state); + }); +}); diff --git a/test/unit/sdam/srv_polling.test.js b/test/unit/sdam/srv_polling.test.js index ccf824a1976..1cbd33083ba 100644 --- a/test/unit/sdam/srv_polling.test.js +++ b/test/unit/sdam/srv_polling.test.js @@ -1,5 +1,6 @@ 'use strict'; +const Promise = require('../../../lib/promise_provider').get(); const Topology = require('../../../lib/core/sdam/topology').Topology; const TopologyDescription = require('../../../lib/core/sdam/topology_description') .TopologyDescription;