-
Notifications
You must be signed in to change notification settings - Fork 1.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
NODE-2579/3.6/global-promise-prereq #2340
Conversation
@@ -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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's a case that I'm taking about. It's a little more comprehensive than it previously was. I read this as, check the options
, then check db
, then check topology
.
return new this.s.promiseLibrary(resolve => { | ||
resolve(); | ||
}); | ||
return Promise.resolve(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was an opinionated change, no reason to call new here just to resolve.
@@ -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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Much cleaner here 😍
withS: { s: { promiseLibrary: Symbol('withS') } } | ||
}; | ||
|
||
describe('PromiseProvider', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a test for PromiseProvider
just testing for the reducer, ensuring it works as expected.
@@ -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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is within collection.find
it's not clear to me which methods you can pass in promiseLibrary
and which you can't. Should this be PromiseProvider.get(this);
or PromiseProvider.get(options, this);
? Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I'm understanding things correctly, in order to keep the behavior unchanged here for collection.find
, it should be PromiseProvider.get(this);
- the original code always sets newOptions.promiseLibrary = this.s.promiseLibrary
regardless of the user passing in options.promiseLibrary
.
Any methods in Collection
which don't call something like options.promiseLibrary = this.s.promiseLibrary;
before executing the operation would need to honor the promiseLibrary
passed in by the user on that method - if there are any such methods.
@@ -42,14 +42,14 @@ const executeOperation = require('./operations/execute_operation'); | |||
* @class | |||
* @return {Admin} a collection instance. | |||
*/ | |||
function Admin(db, topology, promiseLibrary) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Had the idea of removing this promiseLibrary
option, and rather just pull it out of db
.
if (!(this instanceof Admin)) return new Admin(db, topology); | ||
|
||
// Internal state | ||
this.s = { | ||
db: db, | ||
topology: topology, | ||
promiseLibrary: promiseLibrary | ||
promiseLibrary: PromiseProvider.get(db, topology) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought the idea was to remove every instance of promiseLibrary
in favor of getting it from the PromiseProvider
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is my understanding that each time a method takes-in promiseLibrary
it set's it for it's class-scope and it's-children. And I've tried to illustrate this in this example. I thought we were all in-agreement that 3.6 can't change this functionality.
// here mongoClient sets `bluebird`
const client = await MongoClient("mongodb://127.0.0.1", {
promiseLibrary: bluebird,
useUnifiedTopology: true
});
const db = client.db("test");
// here in collection I'm setting the promiseLibrary to `q`
const collectionPromise = db.collection("test", { promiseLibrary: q }).find().toArray();
I would assume here the driver would provide any promises coming from client
to be bluebird and promises coming from collectionPromise
to be q
.
This is the "parody" i believed we we were trying to support in 3.6
If we use a "global" setter / getter, and don't store in-parent-class and pass options, we loose this tracking of what class is using what.
My plan was to remove the stores in master and only .set()
in MongoClient
(and mognodb.Promise = bluebird
module-getter)
This is related to the private-slack thread here.
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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these are frustrating.. they should really be deferred to the execute
method of UpdateOneOperation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should that change be in this PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no, just calling it out
@@ -193,7 +195,7 @@ class Topology extends EventEmitter { | |||
// Active client sessions | |||
sessions: new Set(), | |||
// Promise library | |||
promiseLibrary: options.promiseLibrary || Promise, | |||
promiseLibrary: PromiseProvider.get(options), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A user can't create a Topology
, so the promiseLibrary
provided to this class is only whatever is set on the top level MongoClient
. I think we can remove storage here, and just depend on the PromiseProvider.get
@@ -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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see any need to store the promiseLibrary
for cases where it's not already being accepted in options
. In this case the db
is being stored so I think you can replace future uses in this class with PromiseProvider.get(this.s.db)
lib/promise_provider.js
Outdated
*/ | ||
|
||
const store = { | ||
_promise: null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please use a Symbol instead of a underscore prefixed value, we don't want people changing this by hand:
const kPromise = Symbol('promise');
const Store {
[kPromise]: undefined
}
* @api private | ||
*/ | ||
|
||
const store = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
any reason you used an object instance here, rather than a class with static methods?
class PromiseProvider {
static get(...) {}
}
const kPromise = Symbol('promise');
PromiseProvider[kPromise] = undefined;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I ended up copping mongooses implementation directly after previous comments regarding preferring their file-name convention, and scrapping the getter implementation I had earlier developer. I thought we were trying not to veer off what they had too much. I'd be down to use a class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's use a class!
@@ -0,0 +1,41 @@ | |||
'use strict'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I think this is a unit test, not a functional one
@@ -1,5 +1,6 @@ | |||
'use strict'; | |||
|
|||
const Promise = require('../../../lib/promise_provider').get(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is it possible to use ignorePatterns
to ignore files in test
for this rule? I don't think it buys us anything to force these declarations in our test files, we only want it in our lib
files to provide support to our users.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the opposite of what you mentioned earlier regarding adding these to tests because it' allows us to test the driver using multiple promise implementations.
I'm not sure if it's possible for eslint
to ignore
a specific rule (eslint-plugin-promise
) for a specific folder (tests). Without possibly having two different eslint
configs.
If there's a way to not have to import this in tests, totally I'm down.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whew, this is a big one! I didn't get all the way through the PR, but I'll stop here pending feedback on the items I raised.
lib/promise_provider.js
Outdated
|
||
store.get = function() { | ||
const values = Object.keys(arguments).map(k => arguments[k]); | ||
const result = values.reduce((acq, parent) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is probably negligible in terms of performance, but I'd prefer doing this without the extra array traversal. E.g.
const result = Object.keys(arguments).reduce((acq, k) => {
const parent = arguments[k];
...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, adopting change.
@@ -1,5 +1,6 @@ | |||
'use strict'; | |||
|
|||
const PromiseProvider = require('./promise_provider'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just wanted to point out that this class has been refactored to use maybePromise
so most of these changes will cause merge conflicts once #2333 makes its way into 3.6
. It might be easier to just revert this file and add a /* es-lint-disable promise/no-native */
at the top, along with a note to update once that PR is merged in - not a big deal either way.
@@ -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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I'm understanding things correctly, in order to keep the behavior unchanged here for collection.find
, it should be PromiseProvider.get(this);
- the original code always sets newOptions.promiseLibrary = this.s.promiseLibrary
regardless of the user passing in options.promiseLibrary
.
Any methods in Collection
which don't call something like options.promiseLibrary = this.s.promiseLibrary;
before executing the operation would need to honor the promiseLibrary
passed in by the user on that method - if there are any such methods.
@@ -1990,7 +1994,7 @@ Collection.prototype.parallelCollectionScan = deprecate(function(options, callba | |||
options.readPreference = resolveReadPreference(this, options); | |||
|
|||
// Add a promiseLibrary | |||
options.promiseLibrary = this.s.promiseLibrary; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a little confused about what setting options.promiseLibrary
before running executeLegacyOperation
does. As you can see here, executeLegacyOperation
ignores the options
and grabs the promiseLibrary
from the topology
. If I'm not missing something, we can just delete anything that sets options.promiseLibrary
before a call to executeLegacyOperation
.
Implements
promise-provider
and gets rid of native calls toPromise
, each call needs to be defined within scope useseslint-plugin-promise
to enforce.Because this is a large commit I tried to have a simple commits to make it easier to review:
adds promise_provider
This commit adds the new file
promise_provider
.tests use promise-provider
This commit adds require calling
promise_provider
to the top of every test that uses aPromise
, getting rid of globalPromise
call.uses promise provider & no-native
This commit makes calls to
PromiseProvider.get()
in functions. Fixes some missed returns where natives shouldn't have been used. The functionality ofPromiseProvider.get
is customized to take any number of arguments, these arguments are possible stores forpromiseLibrary
. Some changes may have been made that changes current functionality to methods, either by adding optionalpromiseLibrary
where it should have existed or by adding it where it shouldn't be, open to changing these cases.implements
eslint-plugin-promise
The last commit, adds the
eslint-plugin-promise
rule, which prevents any use of aPromise
that is not defined in scope. Tested and pass lint.