Skip to content
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

feat: refactor core to use Promises instead of callbacks #55

Merged
merged 1 commit into from
Oct 2, 2019

Conversation

dpopp07
Copy link
Member

@dpopp07 dpopp07 commented Oct 1, 2019

Refactors the entire library to use Promises instead of callbacks. This is a pretty big change but since the SDK is really the only "user", I don't think it will cause any problems.

The generated SDK code should be able to change slightly to achieve no difference in current behavior. The current plan is to start returning Promises first in the generated Watson SDK, with callbacks being an accommodation. We also want to start generating new services using only Promises. This PR enables that route, as it moves all callback handling out of the core and into the generated SDK.

If this is too big of a change to make too late in the game, that's fine, but this work is necessary to move towards a Promise-only approach and doing it this way gives us more flexibility in the generated SDKs to use either approach.

If this is agreeable, I will open a corresponding PR in the generator.

FYI: This PR makes heavy use of the fact that every then and catch block returns a Promise itself and that "returning" or "throwing" inside of these blocks, will resolve and reject in a later chained Promise, respectively. Where we were previously passing one callback around to multiple functions, we are now bubbling up results through chained Promises. Using the other method, we used to lose some error information that is now becoming visible. This required some small changes in the unit tests. On that point, I think the code is improved overall.

@dpopp07 dpopp07 requested review from padamstx, mkistler and germanattanasio and removed request for padamstx October 1, 2019 05:21
Copy link
Contributor

@mkistler mkistler left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great!

But I am nervous about making such a large change this close to the major release.
I'd like to let @germanattanasio make the call on that.

@@ -48,11 +48,11 @@ export class BasicAuthenticator extends Authenticator implements AuthenticatorIn
this.password = options.password;
}

public authenticate(options: AuthenticateOptions, callback: AuthenticateCallback): void {
public authenticate(options: AuthenticateOptions): Promise<void> {
const authHeaderString = computeBasicAuthHeader(this.username, this.password);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know about TypeScript but with JS, you need to always wrap the code within a promise in order to catch errors.

public authenticate(options: AuthenticateOptions): Promise<void> { 
  return new Promise((resolve, reject) => {
     const authHeaderString = computeBasicAuthHeader(this.username, this.password); 
     const authHeader = { Authorization: authHeaderString } 
     options.headers = extend(true, {}, options.headers, authHeader);
     resolve();
 });
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Promises work the same in TS as they do JS. That is true, I just wasn't anticipating any errors here and wasn't handling them when I was using callbacks. But I am good with this approach here to be safe.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm just trying to anticipate future changes to those methods that today don't return errors

*/
public getToken(cb: Function) {
public getToken(): Promise<any> {
if (!this.tokenInfo[this.tokenName] || this.isTokenExpired()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be wrap in a promise like my example above

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the requestToken method also returns a Promise, I think it is cleaner to chain the Promises together than to create a new Promise altogether.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if this.tokenInfo is undefined?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would throw an error that would not be rejected in the Promise, so the users rejection handling wouldn't catch it. Which would be bad. But we explicitly define the tokenInfo object in the constructor: this.tokenInfo = {};.

I understand what you're saying, I just don't know how much we want to account for a scenario like this that shouldn't ever happen. We were not accounting for any errors happening here before either. The behavior is staying the same. That said, if you think we need to be able to catch errors there, I will change the code

Copy link
Contributor

@germanattanasio germanattanasio left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left some comments around wrapping the entire function as a promise rather than using Promise.resolve() or Promise.reject()

@dpopp07
Copy link
Member Author

dpopp07 commented Oct 1, 2019

In some scenarios I would think that is better but when returning a series of Promises in a row (like authenticate -> getToken -> requestToken -> sendRequest) I think it makes more sense to use Promise chaining and, from each method, return the Promise returned from the method before it, with handlers to do the method-specific processing. Let me know what you think

@dpopp07 dpopp07 force-pushed the return-promise-send-request branch 3 times, most recently from dc1d437 to d1362d8 Compare October 2, 2019 02:30
Copy link
Contributor

@germanattanasio germanattanasio left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The latest changes look good. Thanks!

@dpopp07 dpopp07 force-pushed the return-promise-send-request branch from d1362d8 to d45348a Compare October 2, 2019 17:48
BREAKING CHANGE: None of the authenticators or request methods take callbacks as arguments anymore - they return Promises instead.
@dpopp07 dpopp07 force-pushed the return-promise-send-request branch from d45348a to bc6803e Compare October 2, 2019 17:51
@dpopp07 dpopp07 merged commit a668ec5 into release-v1-rc2 Oct 2, 2019
@dpopp07 dpopp07 deleted the return-promise-send-request branch October 2, 2019 18:14
dpopp07 added a commit that referenced this pull request Oct 3, 2019
BREAKING CHANGE: None of the authenticators or request methods take callbacks as arguments anymore - they return Promises instead.
dpopp07 added a commit that referenced this pull request Oct 3, 2019
BREAKING CHANGE: None of the authenticators or request methods take callbacks as arguments anymore - they return Promises instead.
ibm-devx-automation pushed a commit that referenced this pull request Oct 3, 2019
# [1.0.0](v0.3.6...v1.0.0) (2019-10-03)

### Bug Fixes

* Move check for serviceUrl to createRequest ([#47](#47)) ([6f04739](6f04739))
* parse result from response in token managers ([6bbe423](6bbe423))
* provide bundlers alternate file for browser support ([#58](#58)) ([88a9d16](88a9d16))

### Build System

* drop support for Node versions 6 and 8 ([#33](#33)) ([d47c737](d47c737))

### Code Refactoring

* look for credentials file in working dir before home dir ([#46](#46)) ([c5556de](c5556de))
* return detailed response as second callback argument ([#34](#34)) ([dc24154](dc24154))

### Features

* add `setServiceUrl` method as a setter for the `serviceUrl` property ([#41](#41)) ([cfb188f](cfb188f))
* add specific error handling for SSL errors with cloud private instances ([#54](#54)) ([056ec9a](056ec9a))
* export `UserOptions` interface from the BaseService ([#50](#50)) ([4f0075a](4f0075a))
* implement new authenticators to handle sdk authentication ([#37](#37)) ([f876b6d](f876b6d))
* refactor core to use Promises instead of callbacks ([#55](#55)) ([9ec8afd](9ec8afd))

### BREAKING CHANGES

* None of the authenticators or request methods take callbacks as arguments anymore - they return Promises instead.
* Users that have credential files in both the working directory and the home directory will see a change in which one is used.
* The internal property `url` no longer exists on the `baseOptions` object, it has been renamed to `serviceUrl`
* The old style of passing credentials to the base service will no longer work. An Authenticator instance MUST be passed in to the base service constructor.
* token managers no longer support user access tokens. use BearerTokenAuthenticator instead
* The class names of the token managers have changed.
* `Icp4dTokenManagerV1` renamed to `Cp4dTokenManager`
* `IamTokenManagerV1` renamed to `IamTokenManager`
* `JwtTokenManagerV1` renamed to `JwtTokenManager`
* The public method `setAuthorizationInfo` is renamed to `setClientIdAndSecret`
* The response body is no longer the 2nd callback argument, the detailed response is. The body is located under the `result` property. The `data` property is removed.
* This SDK may no longer work with applications running on Node 6 or 8.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants