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

Simplify wrapping got #503

Merged
merged 29 commits into from
Jul 8, 2018
Merged

Simplify wrapping got #503

merged 29 commits into from
Jul 8, 2018

Conversation

szmarczak
Copy link
Collaborator

@szmarczak szmarczak commented Jul 6, 2018

@sindresorhus
Copy link
Owner

I'm a little bit worried that we're exposing too many internals now, especially got.normalizeArguments.

@sindresorhus
Copy link
Owner

What's the difference between .fork() and .create()?

@sindresorhus
Copy link
Owner

I really like how much it simplifies gh-got though. I'm sure it will enable more useful niche versions of Got.

@szmarczak
Copy link
Collaborator Author

What's the difference between .fork() and .create()?

Fork - forks the options from parent instance (squashes them into a new piece, performs assignOptions again), replaces methods and handler if provided.

Create - imagine there are no options, no methods, no handler. You set it up from scratch. Like another got, not inherited from any instance.

@sindresorhus
Copy link
Owner

We should list fork() first then, as that's what most end-users would want. They just want to tweak some of the defaults.

@sindresorhus
Copy link
Owner

A common use-case is just overriding a few defaults when creating a new instance. I don't like how you now need to specify a nested object to set a few options.

I think got.fork() should be optimized for the common-case and only allow overriding options, not handler/methods. If users need more advanced control, they can use got.create() and pass in the exposed got.defaults.

So got.fork():

const client = got.fork({headers: {'x-foo': 'bar'});

Also, is fork the best word for this? Some alternatives would be extend() or defaults(). Not saying it's not. Just putting it out there.

options.stream = true;
module.exports = (url, options) => {
const normalizedArgs = normalizeArguments(url, options);
normalizedArgs.stream = true;
Copy link
Owner

Choose a reason for hiding this comment

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

normalizedArgs => normalizedOptions would be more accurate, but that's quite verbose.

What's wrong with just:

options = normalizeArguments(url, options);

Do we need the original options somewhere?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed that.

readme.md Outdated
#### got.create(settings)

Configure a new `got` instance with provided settings:

Copy link
Owner

Choose a reason for hiding this comment

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

Since we plan to use this for gh-got, I think we should just link to it as an example now.

readme.md Outdated

##### methods

Array of supported methods.
Copy link
Owner

Choose a reason for hiding this comment

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

methods => request methods

Copy link
Owner

Choose a reason for hiding this comment

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

Maybe also document how to just use the defaults methods: methods: got.defaults.methods?

source/index.js Outdated
headers: {
'user-agent': `${pkg.name}/${pkg.version} (https://github.com/sindresorhus/got)`
handler: (url, options, next) => {
return next(url, options);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think handler could use next directly. Like handler: next

source/create.js Outdated
}
}
if (options.endpoint) {
url = /^https?/.test(path) ? path : options.endpoint + path;
Copy link
Collaborator Author

@szmarczak szmarczak Jul 7, 2018

Choose a reason for hiding this comment

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

This should be:

	if (options.endpoint && !/^https?/.test(path)) {
		url = options.endpoint + path;
	}

source/create.js Outdated
} catch (error) {
return Promise.reject(error);
}
}

got.create = (options = {}) => create(assignOptions(defaults, options));
got.create = newDefaults => create(newDefaults);
Copy link
Collaborator Author

@szmarczak szmarczak Jul 7, 2018

Choose a reason for hiding this comment

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

This should be:

got.create = create;

source/create.js Outdated
}
}
if (options.endpoint && !/^https?/.test(path)) {
url = options.endpoint + path;
Copy link
Owner

Choose a reason for hiding this comment

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

I think we should do url = (new URLGlobal(path, options.endpoint)).toString() so it handles things like options.endpoint ending in a slash and path starting with a slash. That would end up with https://foo.com//path here (Could you add a test for the specifically too?).

Copy link
Owner

Choose a reason for hiding this comment

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

The above if (options.endpoint && !/^https?/.test(path)) { check can actually be simplified to just if (options.endpoint) { then.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, I was using that in my app but forgot about it :D

source/create.js Outdated
continue;
}
}
if (options.endpoint && !/^https?/.test(path)) {

This comment was marked as outdated.

This comment was marked as outdated.

readme.md Outdated
@@ -99,6 +99,14 @@ Properties from `options` will override properties in the parsed `url`.

If no protocol is specified, it will default to `https`.

##### endpoint
Copy link
Owner

Choose a reason for hiding this comment

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

I'm not sure about the word endpoint anymore. It is too much connected to REST APIs. This one is more generic. Maybe we should follow new URL() and just call it base or baseUrl? I prefer the latter.

readme.md Outdated
Type: `string` `Object`

When specified, `url` will be preceded by `endpoint`.

Copy link
Owner

Choose a reason for hiding this comment

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

Can you also mention that this one is especially useful with got.extend() to create niche specific Got instances?

readme.md Outdated

##### methods

Type: `object`
Copy link
Owner

Choose a reason for hiding this comment

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

Object

readme.md Outdated

##### handler

Type: `function`<br>
Copy link
Owner

Choose a reason for hiding this comment

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

Function

readme.md Outdated
Function making additional changes to the request.

To inherit from parent, set it as `got.defaults.handler`.<br>
To use the default handler, set it as `null` or `undefined`.
Copy link
Owner

Choose a reason for hiding this comment

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

Couldn't this just be: To use the default handler, just omit specifying this.?

readme.md Outdated

##### [options](#options)

To inherit from parent, set it as `got.defaults.options` or use [destructuring assignment](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment).
Copy link
Owner

Choose a reason for hiding this comment

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

use [destructuring assignment]

Did you mean to say "object spread"?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yep, naming is mixed up in my head sometimes

readme.md Outdated

###### [options](#options)

###### next
Copy link
Owner

Choose a reason for hiding this comment

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

next => next()

@sindresorhus
Copy link
Owner

Can you add a small tip to the Tips section (https://github.com/sindresorhus/got#tips) on how to use the endpoint option with got.extend() to quickly get an endpoint specific Got instance? I think that's a common need, so would be good to have a clear example for that specific thing.

readme.md Outdated

Example: [gh-got](https://github.com/sindresorhus/gh-got/blob/master/index.js)

Configure a new `got` instance with provided settings:
Copy link
Owner

Choose a reason for hiding this comment

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

We should be more explicit here about got.create() having no good defaults included by default and how it's different from got.extend().

@brandon93s
Copy link
Contributor

I'm a bit concerned that a user is going to be confused as to whether they should be using get.extend() or got.create() for their use case. Most users are just going to want to provide new defaults, but extend has a small blurb in the docs compared to the in-depth create section which could mislead them. create seems to cater primarily to library / module creation (e.g. gh-got) and not everyday use. It may be time to consider breaking out some advanced sections of the docs into separate files and linking to them to avoid information overload:

Need more control over the behavior of got? Check out the advanced creation options.

Looking elsewhere, axios has axios.create() and request has request.defaults() which are functionally equivalent to got.extend(). Users coming from axios may reach for create and have a very different experience.

readme.md Outdated

Type: `string` `Object`

When specified, `url` will be preceded by `baseUrl`.<br>
Copy link
Contributor

Choose a reason for hiding this comment

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

"will be prepended with baseUrl" is a bit more clear.

Should we note that this only applies to relative URLs? If you specify an absolute URL it will skip the baseUrl.

Copy link
Contributor

Choose a reason for hiding this comment

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

The placement in the docs has this as a separate parameter to the got api. I think it's just part of the options, however. If that's the case it should be moved into the options section below.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, it's an option. Done in next commit.

readme.md Outdated

Configure a new `got` instance with default `options`:
Configure a new `got` instance with default `options` and custom `baseUrl` (optional):
Copy link
Contributor

Choose a reason for hiding this comment

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

See other notes, baseUrl is an option. This feels redundant.

readme.md Outdated

Configure a new `got` instance with default `options`:
Configure a new `got` instance with default `options` and custom `baseUrl` (optional):

```js
(async () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

This example is a bit unclear if you're not familiar with httpbin.org and knowing that this endpoint will reflect headers back in the body. Maybe something like this would help illustrate:

const client = got.extend({
	baseUrl: 'https://example.com',
	headers: {
		'x-unicorn': 'rainbow'
	}
});

client.get('/demo')

/* HTTP Request => 
 * GET /demo HTTP/1.1
 * Host: example.com
 * x-unicorn: rainbow
 */

Copy link
Collaborator Author

@szmarczak szmarczak Jul 7, 2018

Choose a reason for hiding this comment

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

I know about httpbin.org but I didn't think of it. I'll change this to httpbin.org and provide the commented result :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Oh wait, you mean users may not be familiar with httpbin.org? Then yes too. I'll replace that.

source/create.js Outdated
got[method] = (url, options) => got(url, {...options, method});
got.stream[method] = (url, options) => got.stream(url, {...options, method});
}

Object.assign(got, errors);
Object.assign(got, {defaults});
Copy link
Contributor

Choose a reason for hiding this comment

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

Exposing this and having it used in the closure could lead to some unexpected behavior if defaults are modified:

test('tampering with defaults', async t => {
	const instance = got.create({
		handler: got.defaults.handler,
		methods: got.defaults.methods,
		options: { ...got.defaults.options,
			baseUrl: 'example'
		}
	});

	const instance2 = instance.create({
		handler: instance.defaults.handler,
		methods: instance.defaults.methods,
		options: instance.defaults.options
	});

	// Tamper Time
	instance.defaults.options.baseUrl = 'http://google.com'

	t.is(instance.defaults.options.baseUrl, instance2.defaults.options.baseUrl);
});

I think I'd expect two instances created with .create to be independent and not impact each other. Similar tampering could be performed on the global got.defaults that the module exports.

Thoughts?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You're right. We should deep freeze those or clone.

Copy link
Owner

Choose a reason for hiding this comment

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

We can deep clone it with the extend dependency, I think.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Deep cloning may lead to false information because it's still writable. Used deep freeze.

@sindresorhus
Copy link
Owner

@brandon93s Good point. Let's move it out into a separate file like you've described.

@szmarczak
Copy link
Collaborator Author

szmarczak commented Jul 7, 2018

@brandon93s I would leave the names unchanged. Previously got.extend() was named got.fork() (original creator named it got.create() but we need a real got.create() - no defaults). I would just note in readme.md/xxx.md the difference between got.create() and got.extend(). I can rename it anytime if it's needed.

@sindresorhus
Copy link
Owner

@szmarczak I don't think @brandon93s was suggesting we rename it, but rather just pointing out the benefit of moving got.create() out of the readme.

@szmarczak
Copy link
Collaborator Author

@sindresorhus So I'll do that tomorrow then 🦄

source/create.js Outdated
const deepFreeze = obj => {
const keys = Object.keys(obj);

for (let i = 0; i < keys.length; i++) {
Copy link
Owner

Choose a reason for hiding this comment

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

Use a for-of loop and Object.entries().

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Wouldn't it be better: for-of and Object.keys? I don't see much difference.

Copy link
Owner

Choose a reason for hiding this comment

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

Object.entries() is better as it gives you the value too right away, instead of having to do options.headers[key].

source/create.js Outdated
const keys = Object.keys(obj);

for (let i = 0; i < keys.length; i++) {
if (typeof obj[keys[i]] === 'object' && obj[keys[i]] !== null) {
Copy link
Owner

Choose a reason for hiding this comment

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

Use the is module here: is.object


> Make calling REST APIs easier by creating niche-specific `got` instances.

#### got.extend([options])
Copy link
Owner

Choose a reason for hiding this comment

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

got.extend() should stay in the readme, it was only got.create() that supposed to be moved out.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Oh, right.

@sindresorhus sindresorhus merged commit bc41a49 into sindresorhus:master Jul 8, 2018
@sindresorhus
Copy link
Owner

This is perfect now. Amazing work @szmarczak 🙌

leo-celebrate

@szmarczak szmarczak deleted the got-api branch January 17, 2019 18:54
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.

None yet

3 participants