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

Add ability to merge instances #510

Merged
merged 26 commits into from Aug 10, 2018
Merged

Conversation

szmarczak
Copy link
Collaborator

@szmarczak szmarczak commented Jul 9, 2018

WHY?

Imagine there are got plugins. You can limit download & upload, use GitHub API, send random User Agent and many more. The possibilities are infinite. What if you want to merge some of them into one single piece? Well, got.merge() comes with help.

const singleInstance = got.merge([limitInstance, ghInstance, randomUAInstance, ...]);

DONE

TODO

  • Discuss possible alternatives

DROPPED TASKS

Removing instances from a chain
Checking if a chain has chosen instance
Getting an array of used instances: got.instances.

Why these tasks were dropped?

That's because defaults are merged, so it's another single instance now. The instances you have provided aren't stored.

@brandon93s
Copy link
Contributor

What is the use case for this?

E.g. one instance can limit download & upload and second instance may set some headers. Join them through the got.forward function! instanceC = instanceA.forward(instanceB)

Does this imply that an app would be using three instances of got? Seems that this could just be handled by constructing / merging numerous config objects together instead of mashing instances together.

test/create.js Outdated
@@ -99,7 +99,7 @@ test('custom endpoint with custom headers (extend)', async t => {
json: true
})).body;
t.is(headers.unicorn, 'rainbow');
t.is(headers['user-agent'] === undefined, false);
t.false(headers['user-agent'] === undefined);
Copy link
Owner

Choose a reason for hiding this comment

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

t.not(headers['user-agent'], undefined);

Same with the other test.

@sindresorhus
Copy link
Owner

I'm also struggling to imagine a use-case for this. What actual (not just example) problem does this solve? I'm not saying it's not a good feature, I just don't see when I would use it.

@@ -114,3 +112,31 @@ const unicorn = got.create(settings);
// Same as:
const unicorn = got.extend({headers: {unicorn: 'rainbow'}});
```

#### got.forward(instance)
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 forward is a confusing name as it sounds like you're forwarding the request. If this were to be added, it would make more sense to just let got.extend and got.create accept a parent instance.

Copy link
Collaborator Author

@szmarczak szmarczak Jul 9, 2018

Choose a reason for hiding this comment

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

What if you use two different libs (instances of got)? Then you have no possibility to do that. Wait, you could do that this way: got.extend(instance)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

got.extend(instance) people would think it'll only merge options (not handlers), the same as got.extend(instance.defaults.options) but no the same as got.forward(instance). I'll rename it to got.merge.

@szmarczak
Copy link
Collaborator Author

szmarczak commented Jul 9, 2018

@brandon93s You're right, I should've documented that better.

Does this imply that an app would be using three instances of got?

No. But it says so, I need to change the docs then.

Seems that this could just be handled by constructing / merging numerous config objects together instead of mashing instances together.

That's how it's done!

https://github.com/szmarczak/got/blob/074462ae98c8ea27108db17d8d59ee10199bd3c3/source/create.js#L46-L50

Note: this comment is outdated.

@szmarczak
Copy link
Collaborator Author

szmarczak commented Jul 9, 2018

I used to do limit(gotInstance, bytes) to limit download & upload or
process(() => limit(gotInstance, bytes)) when I needed to make request many times + limit that.

Instead of process(() => limit(gotInstance, bytes)) we can do something like

const instance = process.merge(limit);

instance(url, { limit: bytes })

@szmarczak szmarczak changed the title Introduce got.forward(options) Introduce got.merge(options) Jul 9, 2018
@szmarczak
Copy link
Collaborator Author

szmarczak commented Jul 9, 2018

Real example (note: not tested): https://gist.github.com/szmarczak/86217216ad2fbc0c8a3e833e5f290460

@sindresorhus
Copy link
Owner

@szmarczak You got a comment on your gist: https://gist.github.com/szmarczak/86217216ad2fbc0c8a3e833e5f290460#gistcomment-2642454 (Mentioning since there are no notifications for gists).

@sindresorhus
Copy link
Owner

@brandon93s Can you do your gist comment here instead? As it's easier to follow the discussion here.

@szmarczak
Copy link
Collaborator Author

szmarczak commented Jul 10, 2018

@brandon93s Yeah. We could do something like this:

const merge = (a, b) => got.create({
	methods: a.defaults.methods,
	options: got.assignOptions(a.defaults.options, b.defaults.options),
    handler: (options, next) => a.defaults.handler(options, options => b.defaults.handler(options, next));
});

const ghLimit = merge(ghGot, limit);

But consider merging many instances. Think about plugins, like limiting download & upload, switching to HTTPS where possible, new options etc. :)

@szmarczak szmarczak closed this Jul 10, 2018
@szmarczak szmarczak reopened this Jul 10, 2018
@szmarczak szmarczak changed the title Introduce got.merge(options) Merging instances Jul 10, 2018
@szmarczak szmarczak mentioned this pull request Jul 11, 2018
@@ -11,7 +11,8 @@ Configure a new `got` instance with the provided settings.<br>

##### [options](readme.md#options)

To inherit from parent, set it as `got.defaults.options` or use [object spread](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax#Spread_in_object_literals).
To inherit from parent, set it as `got.defaults.options` or use `got.assignOptions(defaults.options, options)`.<br>
Avoid using [object spread](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax#Spread_in_object_literals) as it may give unwanted result.
Copy link
Collaborator Author

@szmarczak szmarczak Jul 13, 2018

Choose a reason for hiding this comment

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

const a = {headers: {cat: 'meow'}};
const b = {headers: {dog: 'woof'}};

{...a, ...b}            // => {headers: {dog: 'woof'}}
got.assignOptions(a, b) // => {headers: {cat: 'meow', dog: 'woof'}}

@szmarczak
Copy link
Collaborator Author

@jstewmon Can you review this? I'd be very grateful if you left some notes 😃

@szmarczak szmarczak force-pushed the got-forward branch 3 times, most recently from e389280 to 39982d8 Compare July 17, 2018 20:00
Copy link
Contributor

@brandon93s brandon93s left a comment

Choose a reason for hiding this comment

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

I'm still not convinced this is necessary. It adds complexity (maintenance risk) to the code base for a very advanced used case. Since extensive documentation is already needed for it, we could offer tips on how to merge instances in the readme as opposed to offering it in core. A separate module could also support this need.

@@ -14,6 +14,8 @@ module.exports = options => {

options.gotRetry.retries = () => 0;

options.gotRetry.retries = () => 0;
Copy link
Contributor

Choose a reason for hiding this comment

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

Isn't this the same as line 15?

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, I forgot to remove it when I was resolving a merge conflict.


#### got.merge(instances, [methods])

Merges many instances into a single one.
Copy link
Contributor

Choose a reason for hiding this comment

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

Would suggest we document the merge strategy. Which options are merged, in what order, etc.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

@szmarczak
Copy link
Collaborator Author

szmarczak commented Jul 18, 2018

It adds complexity (maintenance risk) to the code base for a very advanced used case.

Maybe.

Since extensive documentation is already needed for it, we could offer tips on how to merge instances in the readme as opposed to offering it in core.

👍

A separate module could also support this need.

A separate file? Of course, but it's very hard to do so. create requires merge, merge requires create and so on. I know Node.JS supports circular require() calls, but I don't think it's gonna work.

Another idea:

got.register('functionName', function() {});
got.functionName();

@sindresorhus
Copy link
Owner

sindresorhus commented Jul 18, 2018

There might be use-cases for this, but I'm a little bit reluctant as this locks us into supporting the ability to arbitrarily merge instances. There might be features we want to add in the future that makes this difficult. Like @brandon93s said, it also adds some maintenance overhead. And we have yet to figure out a way to support plugins, which might or might not overlap or conflict with this.

So in short, I just feel like this is rushing into supporting a huge API surface without enough time and exploration to see what problems it solves and the alternatives.

@sindresorhus
Copy link
Owner

Imagine there are got plugins. You can limit download & upload, use GitHub API, send random User Agent and many more.

We should consider what other ways there are to solve these problems.

@szmarczak
Copy link
Collaborator Author

szmarczak commented Jul 18, 2018

There might be use-cases for this, but I'm a little bit reluctant as this locks us into support the ability to arbitrarily merge instances.

Indeed.

There might be features we want to add in the future that makes this difficult.

That's with every single feature, isn't it? :)

And we have yet to figure out a way to support plugins, which might or might not overlap or conflict with this.

So in short, I just feel like this is rushing into supporting a huge API surface without enough time and exploration to see what problems it solves and the alternatives.

Understood. Better to consider all the alternatives we have here. I'll leave this PR open. This feature is too complicated to release it now.

Tomorrow I'll send two PRs with these changes:

  • expose assignOptions to enable proper options merging (custom instances)
  • better handler function: (normalizedOptions, next) instead of (options, next)
    • move baseUrl option from main handler to normalize-arguments.js


##### [[methods]](#methods)

Default: `instances[0].defaults.methods`
Copy link
Owner

Choose a reason for hiding this comment

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

What's the use-case for this?

Copy link
Collaborator Author

@szmarczak szmarczak Aug 9, 2018

Choose a reason for hiding this comment

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

It's not needed at all (that was an old idea). These are just an aliases to the instance. (got.get(), got.post() etc) Hang on, I'll fix it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Removed.


##### [instances](readme.md#instances)

## Usage
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Should the examples be moved to the top?

@@ -102,7 +102,6 @@ test('extend merges URL instances', t => {
test('create', async t => {
const instance = got.create({
options: {},
methods: ['get'],
Copy link
Owner

Choose a reason for hiding this comment

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

Why are you suddenly removing all the methods fields?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

These are just aliases. If methods are set to ['get'] you can't do got.post() but you can do got(url, {method: 'POST'});. I see no reason why we should let users to modify the aliases.

Copy link
Owner

Choose a reason for hiding this comment

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

I agree 👍

@@ -11,7 +11,7 @@ const urlToOptions = require('./url-to-options');
const isFormData = require('./is-form-data');

const retryAfterStatusCodes = new Set([413, 429, 503]);
const knownHookEvents = ['beforeRequest'];
const knownHookEvents = require('./known-hook-events');
Copy link
Owner

Choose a reason for hiding this comment

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

require statements should be at the top of the file. In this case, line 12.

- options are merged using [`got.mergeOptions()`](readme.md#gotmergeoptionsparentoptions-newoptions) (+ hooks are merged too),
- handlers are stored in an array.

## Usage
Copy link
Owner

Choose a reason for hiding this comment

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

Examples

@@ -111,3 +112,119 @@ const unicorn = got.create(settings);
// Same as:
const unicorn = got.extend({headers: {unicorn: 'rainbow'}});
```

### Merging instances
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 add an entry to the Highlights section in the readme called Composable, which links to this section?

@@ -111,3 +112,119 @@ const unicorn = got.create(settings);
// Same as:
const unicorn = got.extend({headers: {unicorn: 'rainbow'}});
```

### Merging instances

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 it would be nice to have something like this as an intro. Feel free to improve it.

Got supports composing multiple instances together. This is very powerful. You can create a client that limits download speed and then compose it with an instance that signs a request. It's like plugins without any of the plugin mess. You just create instances and then compose them together.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Sounds good!

- handlers are stored in an array.

## Usage

Copy link
Owner

Choose a reason for hiding this comment

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

Some examples of what kind of instances you could compose together.

});
```

#### Limiting download & upload (in case your machine's got a little amount of RAM)
Copy link
Owner

Choose a reason for hiding this comment

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

(in case your machine's got a little amount of RAM) should be in text below the title.


If these ^^^ are different modules and you don't want to rewrite them, use `got.mergeInstances()`.

**Note**: `noUserAgent` must be placed at the end of chain as the instances are merged serially. Other modules do have the `user-agent` header.
Copy link
Owner

Choose a reason for hiding this comment

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

noUserAgent => The noUserAgent instance

Copy link
Owner

Choose a reason for hiding this comment

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

are merged serially => are merged in order

Copy link
Owner

Choose a reason for hiding this comment

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

Other modules => Other instances

```

If these ^^^ are different modules and you don't want to rewrite them, use `got.mergeInstances()`.

Copy link
Owner

Choose a reason for hiding this comment

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

There should be a header here called #### Putting it all together or something.

@sindresorhus
Copy link
Owner

This is looking good now :)

@sindresorhus sindresorhus merged commit f447378 into sindresorhus:master Aug 10, 2018
sindresorhus pushed a commit that referenced this pull request Aug 10, 2018
sindresorhus pushed a commit that referenced this pull request Aug 10, 2018
@szmarczak szmarczak deleted the got-forward branch January 17, 2019 18:55
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