-
Notifications
You must be signed in to change notification settings - Fork 308
Conversation
- Refactor library codebase to use native ES6 syntax, including classes - Remove dependency on lodash functions - Update dependencies to latest versions - Refactor test code to use `const`, arrow functions where possible - Normalize codebase formatting using prettier
Added tests to ensure that, for methods which support callbacks and return promises, that both the callback and promise give the expected behavior. Reorganized test suite to use `tdd` test ui and use mocha's `spec` reporter for better printout of test hierarchy.
…c.js`. Also move mocks JSON into a new folder
lib/clients/authenticated.js
Outdated
callback = orderID; | ||
callback(new Error('must provide an orderID or consider cancelOrders')); | ||
return; | ||
if(!orderID || typeof orderID == 'function') { |
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 the typeof 'function' check necessary here?
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 think their original intent was to ensure that the first argument was an orderID instead of a callback. So if orderID
were a function, then !orderID === true
, so you have to also check to make sure it's not a function.
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.
ahh yeah right, I didn't think about it enough 👍
lib/clients/authenticated.js
Outdated
return prototype.get.call(self, ['accounts'], callback); | ||
}; | ||
getAccounts(callback) { | ||
return this.get(['accounts'], callback); |
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.
thoughts on using arrow syntax in classes?
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.
How so? If you mean something like
class Whatever {
getAccounts: callback => this.get(['accounts'], callback);
}
I'm pretty sure that's invalid JS syntax.
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.
class whatever {
func = prop => this.otherfunc()
}
works?
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.
Not until ES Class Fields are finalized (currently stage 2). Would (/ will?) be nice though.
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.
Jumping between babelified React Native, React and Node can get confusing 😅
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 feel your pain!
lib/clients/authenticated.js
Outdated
|
||
args = args || {} | ||
if (!callback && (typeof args === 'function')) { | ||
async cancelAllOrders(args = {}, callback) { |
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.
😍
lib/clients/authenticated.js
Outdated
return; | ||
} | ||
totalDeletedOrders.push(...data); | ||
} while (data.length); |
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.
elegant while!
lib/clients/authenticated.js
Outdated
callback(new Error('must provide an orderID or consider getOrders')); | ||
return; | ||
getOrder(orderID, callback) { | ||
if (!orderID || typeof orderID == 'function') { |
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.
again on the use of the orderID function check.
args = args || {} | ||
if (!callback && (typeof args === 'function')) { | ||
getProductOrderBook(args = {}, callback) { | ||
if (!callback && typeof args === 'function') { |
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 couple lines of code are duplicated so much in this repo in general, thoughts on what could be done to reduce that in a functional way?
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.
In terms of optimizing syntactic aesthetics, decorators are the first thing I think of,
@optionalArgs
someFunction(args, otherParam, callback) {
/* ... */
}
But even though decorators are in Stage 2, there isn't great out-of-the-box support for transpiling them quite yet, including in the latest version of babel.
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.
Yeah definitely something for the future 🌈 🦄
// rate-limited, try again | ||
setTimeout(() => { | ||
fetchTrades.call(this, stream, tradesFrom, tradesTo, shouldStop); | ||
}, 900); |
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.
why 900? Just curious about the setTimeout.
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.
Not sure, that's the timeout that was used previously.
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.
Hmm interesting. 👍
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.
The rate limit is per second, this skips to the next second. Not sure if this is the ideal solution, we could also just trigger a couple more requests.
var self = this; | ||
self.emit('open'); | ||
onOpen() { | ||
this.emit('open'); |
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.
confused at this here... onOpen
is called when 'open' is emitted, and then it immediately emits 'open' again?
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.
Hmm, now that you point that out, I'm confused as well. This was the previous behavior. Thoughts @fb55?
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.
Sorry for the long wait — this is looking really promising (no pun intended). I left some comments, but this should be good to go soon.
How do you feel about integrating #58 (with default whitespace settings, I didn't see that we were using two spaces here)?
edit: Also, you will need to set the minimum node version for circle to pass.
lib/clients/authenticated.js
Outdated
do { | ||
// Have to use a special promise to get response as well as data | ||
// for later passing to the callback; | ||
[response, data] = await new Promise((resolve, reject) => { |
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'd prefer to not use async/await in this library for now (node@6 should be the target).
lib/clients/authenticated.js
Outdated
this.delete(['orders'], opts, (err, response, data) => { | ||
if (err) reject(err); | ||
resolve([response, data]); | ||
}).catch(() => {}); // silently ignore, rejection is already handled in callback |
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.
Ideally this would be appended every time we pass a callback.
lib/clients/authenticated.js
Outdated
if (!callback) { | ||
throw "Must supply a callback" | ||
} | ||
if (!callback) callback = () => {}; |
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 handle this in makeRequestCallback
lib/clients/public.js
Outdated
if (!started) { | ||
started = true; | ||
fetchTrades(self, rs, tradesFrom, tradesTo, shouldStop, 0); | ||
fetchTrades.bind(this)(rs, tradesFrom, tradesTo, shouldStop, 0); |
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.
.call
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.
Use fetchTrades.call
here.
// rate-limited, try again | ||
setTimeout(() => { | ||
fetchTrades.call(this, stream, tradesFrom, tradesTo, shouldStop); | ||
}, 900); |
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.
The rate limit is per second, this skips to the next second. Not sure if this is the ideal solution, we could also just trigger a couple more requests.
lib/clients/websocket.js
Outdated
const EventEmitter = require('events').EventEmitter; | ||
const Websocket = require('ws'); | ||
const Utils = require('../utilities.js'); | ||
const signRequest = require('../../lib/request_signer').signRequest; |
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.
Destructuring for EventEmitter
and signRequest
— should also be enforced by eslint (prefer-destructuring
).
- Add engine `node >= 6.0.0` - Refactor out (async/await) --> es2015 -`extend:eslint-recommended` - Refactor a bit of code to match
Hmm, CircleCI seems to have independently fallen down on this one ("An error occurred while fetching information about this build from GitHub."). I spun up my own Circle instance and it seems to be working fine, though. Can anyone with access go in and trigger a re-build? |
Side note: It seems that Circle doesn't read node engine settings from |
Also introduce a few wording, stylistic tweaks. Correct markdown syntax
Also add relevant test case This addresses coinbase#23 and coinbase#66
If a client method is invoked with a provided callback, the ultimately- returned promise will never be fulfilled (neither resolved nor rejected). This prevents potential `UnhandledPromiseRejectionWarning`s, which will cause future versions of Node to terminate.
Just checking in - would anyone have time to review these newer changes? If all is well, I propose merging this branch and getting a new release out very soon, possibly with some other addressed issues as well. Thinking towards the future, I also still have nikulis#1 open (based on this branch) which implements the more standard two-argument callback API (major breaking change). There are many other open issues that I would like to work on, but I am trying to limit the scope of this PR itself. If the maintainers are interested in moving forward with this PR (as is my impression), I think it would be valuable to get it through soon; there seem to be many community members submitting PRs based off of the current master which would all need significant refactoring in order to apply here, and I think it would be fairer to them to not have to duplicate so much work going forward. That being said, I'm always happy to help with related tasks too :) Thanks! |
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.
Sorry that it took this long to do another review. I very much like where this is going, and I agree this should land soon to avoid merge conflicts.
We should revisit the promise interface, plus there is one remaining bug (a missing .bind
). Still, this should be good to go very soon.
README.md
Outdated
|
||
try { | ||
|
||
let products = await publicClient.getProducts(); |
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.
We should standardise to either use let
or const
in this file. I'd opt for const
.
lib/clients/authenticated.js
Outdated
opts = opts || {}; | ||
if (!callback && (typeof opts === 'function')) { | ||
request(method, uriParts, opts = {}, callback) { | ||
if (!callback && typeof opts == 'function') { |
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.
===
for typeof
checks
lib/clients/public.js
Outdated
callback(err, response, data); | ||
return; | ||
} | ||
if (err) 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.
if
/else
should always have blocks.
lib/clients/public.js
Outdated
callback(err, response, data); | ||
if (typeof callback == 'function') { | ||
callback(err, response, data); | ||
return; |
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.
We should still fulfil the promise. What do you think about
- Having the signed
request
simply call the unsigned variant (super.request
) - Use superagent's promises directly:
- Create the promise, store a reference
- If there is a callback, add handling for it in a
then
callback - Return the original promise (not the one with error handling applied) — this will ensure that errors are always forwarded
lib/clients/public.js
Outdated
if (!started) { | ||
started = true; | ||
fetchTrades(self, rs, tradesFrom, tradesTo, shouldStop, 0); | ||
fetchTrades.bind(this)(rs, tradesFrom, tradesTo, shouldStop, 0); |
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.
Use fetchTrades.call
here.
tests/websocket.spec.js
Outdated
test('subscribes to the default product (BTC-USD) if undefined', function(done) { | ||
var server = testserver(++port, function() { | ||
var websocketClient = new Gdax.WebsocketClient(null, 'ws://localhost:' + port); | ||
test('subscribes to the default product (BTC-USD) if undefined', function( |
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.
All of these should be switched to arrow functions (eslint can also take care of that — only named functions should still be allowed).
.eslintrc.json
Outdated
"root": true, | ||
"extends": "eslint:recommended", | ||
"env": { | ||
"commonjs": true, |
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.
Pretty sure this is implied by node
.eslintrc.json
Outdated
"ecmaVersion": 2015 | ||
}, | ||
"rules": { | ||
"constructor-super": "warn", |
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.
All of these should error.
var callback = function(err, response, data) { | ||
// your code here. | ||
}; | ||
|
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.
Perhaps add some description here: "Promises can be used with callbacks, as well as using async/await
in modern environments." (or something similar).
lib/clients/public.js
Outdated
if (err) reject(err); | ||
if (data === null) | ||
reject(new Error('Response could not be parsed as JSON')); | ||
else resolve(data); |
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've been looking into replacing parts of this library with swagger-codegen. Their promise interface returns a {data, response}
object — I guess we should adopt that as well. While I like the simplicity of this API, there probably are cases where you want the request object (for headers, status codes etc.), plus an object would allow for future extensions.
All eslint rules now error instead of warn - Add eslint rule `curly: all` - require block statements for control structures (`if`/`else`, etc.) - Add eslint rule `eqeqeq` - only strict equality operators - Add eslint rule `func-names: always` - no anonymous `function` expressions - Add eslint rule `prefer-arrow-callback: { allow-named-functions: true }` Add `eslint` to list of `lint-staged` actions to be enforced before commits
Remove duplicate request logic from `AuthenticatedClient`; delegate to `super` request method
Add some more descriptive text Use `const` over `let` in code examples
Refactor to use promise interface on public/authed clients' `.getProductOrderBook` Update test mock ws server to respond with expected format (with `product_id` data) Remove unnecessary call to `Util.determineProductIds`
return prototype.get.call(self, ['products'], callback); | ||
}; | ||
if (callback) { | ||
p.catch(() => {}); |
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 definitely sensible behaviour, although I'd simply add listeners to the promise in case there is a callback. Still return the original promise (not the one with the callbacks applied) so that nothing needs to be returned from the callback.
We could also get rid of makeRequestCallback
in favour of superagent promises — how do you feel about that?
One comment, but that can be iterated upon once this is merged. There is a merge conflict with the |
Finally got around to merging this. Thanks a lot @awitherow and especially @nikulis! I'd like to land the superagent switch before publishing it — @nikulis do you want to open a PR with the code you already have? |
Thanks to @nikulis I was just giving some insight! He did the 💪. 👏 |
I was holding off on superagent for this one because those using v0.4.x might be relying upon the currently-exposed I did switch to superagent with the new two-parameter callback API in nikulis#1. That branch is a little stale atm, so I'm about to work on getting it up-to-date before I open a new PR in this repo. (Just got back online after a few days off 😅 ) And yes, thank you to all of you, too, for providing all the wonderful feedback and critiques! |
Here's my first pass at adding in promise support re: #41.
The main area of interest for this PR is the
tests
directory (re: discussion on #55), where I've reorganized the test suites and added tests that exemplify uses of both callbacks and promises where applicable.When I started work on this, I had not yet realized that the blocking performance regressions on #40 were due to how tests were running instead of introduced code complexity, so I did my own port to ES2015/17 (also just to get myself familiar with the codebase). I don't mean to replace the hard work others have done on #40, and would be happy to port my changes over to that branch after the maintainers have decided on Node version compatibility and updated the CI to build accordingly.