-
-
Notifications
You must be signed in to change notification settings - Fork 376
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
Allow extend() to add hooks #408
Comments
The problem is that both scenarios are valid. A user might want to replace existing hooks and they might want to accumulate. If we support what you propose, there will be no way to replace existing hooks. I'm open to suggestions on how to solve that. Either way, we should more clearly document the behavior. |
I do agree it makes sense to merge the hooks arrays by default. Maybe we could do something like this to support replacing: import ky, {replaceHooks} from 'ky';
const rootKy = ky.create({
prefixUrl: '/api',
timeout: false,
hooks: {
beforeRequest: [pruneRequestPayload],
beforeRetry: [logError],
},
});
export const kyWithSerializer = rootKy.extend({
hooks: {
beforeRequest: replaceHooks([requestToSnakeCase]),
afterResponse: [responseToCamelCase],
},
}); Where |
@sindresorhus, yeah I like this idea. it's granular to handle each hooks separately. |
Both scenarios are definitely valid use cases, and I can understand the argument that I suppose in Ky we could have an equivalent Another approach would be to allow the user to pass in a function which is given the default / existing list and returns the new list after any replace / accumulate logic. This is essentially inversion of control. import ky from 'ky';
const rootKy = ky.create({
prefixUrl: '/api',
timeout: false,
hooks: {
beforeRequest: [pruneRequestPayload],
beforeRetry: [logError],
},
});
export const kyWithSerializer = rootKy.extend({
hooks: {
// spread the existing hooks to keep them (accumulate) or omit them to overwrite (replace)
beforeRequest: (beforeRequestHooks) => [...beforeRequestHooks, requestToSnakeCase],
afterResponse: [responseToCamelCase],
},
}); |
@sholladay But I've also thought about "passing a callback function" technique. One thing which concerns me is what about the hooks that are not mentioned at all. for example, hooks: {
beforeRetry: (beforeRetry) => [...beforeRetry]
} PS: I like the |
IMO, the |
@sholladay @sindresorhus
|
👍 |
I'm curious why you don't like it? As I find it quite elegant. Definitely more elegant than Although, accepting a function would work too, and it has the added benefit of allowing more merging cases. |
What do you guys think about a |
@agierlicki I think it needs to be more fine-grained than that. |
What about follow up? |
I'm not sure when this changed (the move to Typescript obfuscated the blame history a little), but the library now works the way the ticket author requested: const client1 = ky.create({ hooks: {
beforeRequest: [ () => console.log('before 1') ],
afterResponse: [ () => console.log('after 1') ],
}});
const client2 = client1.extend({ hooks: {
beforeRequest: [ () => console.log('before 2') ],
afterResponse: [ () => console.log('after 2') ],
}});
await client1.get('https://www.google.com');
// before 1
// after 1
await client2.get('https://www.google.com');
// before 1
// before 2
// after 1
// after 2 Unfortunately, as pointed out earlier, this does mean there's no way to remove these hooks. My intuition said to try doing as headers do (also hinted to by Seth above) and explicitly specifying Do we just need another specialized |
Sounds like it just needs to be coerced to an empty array somewhere.
Ugh, I hope not. 😅 As a maintainer, I find this part of the code hard to reason about. As a user, it's convenient until it isn't, since the behavior is so magical. I would like to see us move towards an API where So, for example, to achieve the expected result from the original issue: import ky from 'ky';
// Root Ky
const rootKy = ky.create({
prefixUrl: '/api',
timeout: false,
hooks: {
beforeRequest: [pruneRequestPayload],
beforeRetry: [logError],
},
});
// Ky which serializes request and response
export const kyWithSerializer = rootKy.extend(({ hooks }) => {
return {
hooks: {
...hooks,
beforeRequest: [...hooks.beforeRequest, requestToSnakeCase],
afterResponse: [responseToCamelCase],
}
};
}); And if you wanted to delete a hook type, just set it to import ky from 'ky';
// Root Ky
const rootKy = ky.create({
prefixUrl: '/api',
timeout: false,
hooks: {
beforeRequest: [pruneRequestPayload],
beforeRetry: [logError],
},
});
// Ky which doesn't run any hooks for retries
export const kyWithoutRetryHooks = rootKy.extend(({ hooks }) => {
return {
hooks: {
...hooks,
beforeRetry: undefined,
}
};
}); And now you can even delete a single, specific hook: import ky from 'ky';
// Root Ky
const rootKy = ky.create({
prefixUrl: '/api',
timeout: false,
hooks: {
beforeRequest: [pruneRequestPayload],
beforeRetry: [logError],
},
});
// Ky which doesn't log errors for retries
export const kyWithoutRetryLogs = rootKy.extend(({ hooks }) => {
return {
hooks: {
...hooks,
beforeRetry: hooks.beforeRetry.filter(hook => hook !== logError),
}
};
}); And if a deep merge is truly necessary, it can still be done, just with less magic: import ky from 'ky';
// Root Ky
const rootKy = ky.create({
prefixUrl: '/api',
timeout: false,
hooks: {
beforeRequest: [pruneRequestPayload],
beforeRetry: [logError],
},
});
// Ky which serializes request and response
export const kyWithSerializer = rootKy.extend((options) => {
return deepMerge(options, {
hooks: {
beforeRequest: [requestToSnakeCase],
afterResponse: [responseToCamelCase],
}
});
}); Is it a little more verbose in some cases? Sure. But it's very flexible, makes deleting easy, and makes it obvious what's going on. Ky could This approach would solve various issues: |
@sholladay Hm... I agree, this is actually what I've done in a wrapper library already. But OTOH that would be a breaking change that directly inverts the behavior of an existing method, and I'd argue it isn't what many people would want behavior-wise (of course, maintainers are ultimately the deciding force in any project). What would you say about continuing with the existing BC feature PR to update the behavior of It still brings a maintenance cost, admittedly, but that way we can get everyone an improved API without a breaking change while leaving the existing behavior for those who prefer it. Probably wouldn't be as concerned if it weren't a project with 1M+ weekly downloads. 😅 Footnotes |
With PR #611 merged, you can now pass a function to
@Kenneth-Sills that all sounds good to me. 👍 I'm going to close this, as the thread is getting a bit long/old and the OP's issue was fixed some time ago. Feel free open a new issue for changes to |
* feat: allow explicitly unsetting hooks on `.extend()` The implementation here is pretty simple: just give hook merging some custom logic so it either merges OR resets to an empty array. Matches what was discussed in the related ticket. Documentation has been updated to describe this behavior as well. The dedicated test is just a modified copy of the existing `ky.extend()` one. Admittedly, I did modify a couple of other tests to cover the edge cases instead of building brand new tests. No reason other than noticing they were already close to what I needed and I'm being lazy. Closes #408 * chore: remove unused import
* feat: allow explicitly unsetting hooks on `.extend()` The implementation here is pretty simple: just give hook merging some custom logic so it either merges OR resets to an empty array. Matches what was discussed in the related ticket. Documentation has been updated to describe this behavior as well. The dedicated test is just a modified copy of the existing `ky.extend()` one. Admittedly, I did modify a couple of other tests to cover the edge cases instead of building brand new tests. No reason other than noticing they were already close to what I needed and I'm being lazy. Closes sindresorhus#408 * chore: remove unused import
Two Code Snippets
Expectation:
kyWithSerializer
to also executepruneRequestPayload
in beforeRequest hook and should havebeforeRetry
present tooActual:
beforeRequest
hook array is completely replaced with new arrayThe text was updated successfully, but these errors were encountered: