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

[core-http] avoid mutating global fetch #9880

Merged
merged 6 commits into from
Jul 31, 2020

Conversation

jeremymeng
Copy link
Member

@jeremymeng jeremymeng commented Jul 2, 2020

Use import node_fetch from "node-fetch" instead of import "node-fetch".

Resolves #5143.

Use `import node_fetch from "node-fetch" instead of `import "node-fetch"`.
@@ -4,21 +4,20 @@
import * as tough from "tough-cookie";
import * as http from "http";
import * as https from "https";
import "node-fetch";
import node_fetch from "node-fetch";
Copy link
Member Author

Choose a reason for hiding this comment

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

used node_fetch to avoid conflict with the global fetch

}

const globalWithFetch = global as GlobalWithFetch;
if (typeof globalWithFetch.fetch !== "function") {
const fetch = require("node-fetch").default;
globalWithFetch.fetch = fetch;
globalWithFetch.fetch = node_fetch;
Copy link
Member

Choose a reason for hiding this comment

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

but... why do this? Can't we just use node_fetch directly? This class is only used in node where fetch isn't defined.

Copy link
Member Author

Choose a reason for hiding this comment

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

I actually tried that first, however, the Request type defined in node-fetch isn't compatible with Request from DOM because it defined additional properties. node-fetch v3.0 doesn't seem to have these.

Can I safely suppress/ignore this error?

src/nodeFetchHttpClient.ts:91:23 - error TS2345: Argument of type 'RequestInfo' is not assignable to parameter of type 'import("/home/azuser/git/jssdk/common/temp/node_modules/.pnpm/@types/node-fetch@2.5.7/node_modules/@types/node-fetch/index").RequestInfo'.
  Type 'Request' is not assignable to type 'RequestInfo'.
    Type 'Request' is missing the following properties from type 'Request': context, compress, counter, follow, and 6 more.

91     return node_fetch(input, init);

I now realized that current PR still modifies global so won't fix the issue.

Copy link
Member

Choose a reason for hiding this comment

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

It seems like the trouble is export type CommonRequestInfo = Request | string; in fetchHttpClient yeah? I think we need to either define our own input interface or re-use node-fetch's

Copy link
Member Author

Choose a reason for hiding this comment

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

Our base class FetchHttpClient has this abstract method so we need to have two types that are compatible with both browsers and Node.

   abstract async fetch(input: CommonRequestInfo, init?: RequestInit): Promise<Response>;

I could narrow down to common things that work for both DOM and NodeJs for input: change to input: string because it's the only type we ever use. However it seems harder to do similar for RequestInit as many things are incompatible.

Copy link
Member Author

Choose a reason for hiding this comment

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

url: string is more accurate

Copy link
Member Author

Choose a reason for hiding this comment

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

/cc @willmtemple who made changes around this previously.

Copy link
Member Author

Choose a reason for hiding this comment

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

The returned type Response is not compatible either between browser and Node. So we would need three types that are common/intersection of browser and Node.

Copy link
Member Author

Choose a reason for hiding this comment

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

So I got rid of the type errors by defining common types with some of the properties as any. Looks very hacky but not sure whether there are better ways.

@@ -40,6 +40,7 @@ describe("defaultHttpClient (node)", function() {
});

const client = new DefaultHttpClient();
(client as any)._fetch = httpMock.getFetch();
Copy link
Member Author

Choose a reason for hiding this comment

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

I originally added a DefaultHttpClient constructor to allow passing in the mocked fetch, but felt changing public api just for testing seems too much.

Copy link
Member

Choose a reason for hiding this comment

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

Honestly I think I'd like the constructor parameter better, since at the moment it's very hard to find all the places that set the secret property, since TS can't alias through any and the language service will claim it's never set.

Copy link
Member Author

Choose a reason for hiding this comment

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

Changed to allow passing in fetch as a constructor parameter.

Copy link
Member Author

Choose a reason for hiding this comment

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

Changed again. Now mocking the fetch() instance method.

@jeremymeng
Copy link
Member Author

@xirzec @bterlson would the current approch work, or there are better approaches?

// returns the locally mocked fetch instance
getFetch(): typeof node_fetch {
/// @ts-ignore
return this._fetch as typeof node_fetch;
Copy link
Member Author

Choose a reason for hiding this comment

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

I had to ignore a type error here otherwise, the fetch-mock type is not compatible with node-fetch.

Copy link
Member

@xirzec xirzec left a comment

Choose a reason for hiding this comment

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

Sorry that this review fell off my radar. I think if we can address the commented issue this PR should be mergeable.

@@ -167,10 +168,13 @@ export function createPipelineFromOptions(pipelineOptions: InternalPipelineOptio
//
// @public (undocumented)
export class DefaultHttpClient extends FetchHttpClient {
constructor(_fetch?: typeof node_fetch);
Copy link
Member

Choose a reason for hiding this comment

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

this is the only part that feels bad to me. I don't think we should have test-only stuff in the default constructor, especially not fetch-specific details.

If you already have getFetch as a method of the class, can't you just use sinon to override that method during testing instead of passing the thing explicitly here?

Copy link
Member Author

Choose a reason for hiding this comment

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

Agree. I didn't like changing the constructor. I will explore using sinon.

Copy link
Member Author

Choose a reason for hiding this comment

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

getFetch() was private I think. However I found fetch() is simple enough so I mocked it instead.

Copy link
Member

@xirzec xirzec left a comment

Choose a reason for hiding this comment

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

Thanks for the update! This looks much nicer!

I feel like while we could still refactor this to be a little cleaner, it's not worth the effort since it's only test code that is messy now.

@jeremymeng
Copy link
Member Author

I feel like while we could still refactor this to be a little cleaner, it's not worth the effort since it's only test code that is messy now.

Yeah, I have duplicated the getMockedHttpClient() and was thinking it might not be worth it to share just for two tests.

Copy link
Contributor

@chradek chradek left a comment

Choose a reason for hiding this comment

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

Had a minor comment you can take or leave. Looks good, ship it!

sdk/core/core-http/src/nodeFetchHttpClient.ts Outdated Show resolved Hide resolved
Co-authored-by: chradek <51000525+chradek@users.noreply.github.com>
@jeremymeng jeremymeng merged commit d66de69 into Azure:master Jul 31, 2020
@jeremymeng jeremymeng deleted the no-global-fetch branch July 31, 2020 22:02
jeremymeng added a commit to jeremymeng/ms-rest-js that referenced this pull request Jan 29, 2021
Porting fix from Azure/azure-sdk-for-js#9880.
Use import node_fetch from "node-fetch" instead of import "node-fetch".

Resolves Azure#383
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.

Electron app error: Failed to execute 'fetch' on 'Window': member signal is not of type AbortSignal.
3 participants