-
-
Notifications
You must be signed in to change notification settings - Fork 362
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
Actually cancel the request after the timeout #27
Conversation
Awesome! Can you add a test? |
index.js
Outdated
@@ -99,6 +105,10 @@ class Ky { | |||
...otherOptions | |||
}; | |||
|
|||
if (supportsCancel) { | |||
this._options.signal = abortCtrl.abort(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would override the user's signal
.
index.js
Outdated
@@ -1,6 +1,7 @@ | |||
'use strict'; | |||
|
|||
const isObject = value => value !== null && typeof value === 'object'; | |||
const supportsCancel = typeof AbortController === 'function'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
supportsCancel
=> supportsAbortController
index.js
Outdated
@@ -77,6 +78,11 @@ class TimeoutError extends Error { | |||
} | |||
} | |||
|
|||
let abortCtrl; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
abortCtrl
=> abortController
I need help with writing the tests |
index.js
Outdated
@@ -99,6 +105,10 @@ class Ky { | |||
...otherOptions | |||
}; | |||
|
|||
if (supportsAbortController && !this._options.signal) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We still want timeout to work, even if the user sets their own signal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ookay....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Linting is failing because of this line
There is no addEventListener method on type AbortSignal
How is this PR coming along, @ashinzekene anything I can do to help? |
I'm waiting for a review. You can also look at helping with tests |
@felixfbecker You have experience with AbortController, is this the correct way to handle it? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The signal should probably also be passed to delay()
in the retry logic
index.js
Outdated
@@ -77,12 +79,21 @@ class TimeoutError extends Error { | |||
} | |||
} | |||
|
|||
let abortController; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A global AbortController for all requests? That seems wrong. It should be one controller per request
index.js
Outdated
const delay = ms => new Promise(resolve => setTimeout(resolve, ms)); | ||
|
||
const timeout = (promise, ms) => Promise.race([ | ||
promise, | ||
(async () => { | ||
await delay(ms); | ||
if (!isAborted && supportsAbortController) { | ||
abortController.abort(); | ||
isAborted = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't isAborted
the same as abortController.signal.aborted
?
index.js
Outdated
@@ -99,6 +110,14 @@ class Ky { | |||
...otherOptions | |||
}; | |||
|
|||
if (supportsAbortController) { | |||
if (this._options.signal) { | |||
this._options.signal.addEventListener('abort', () => !isAborted && abortController.abort()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
abort()
is idempotent so the isAborted
check is not needed.
I believe this fixes the issues right ? @felixfbecker |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be possible to add a test for this? For example, if you set the timeout 0 or 1ms then wait, you should be able to assert that the Promise rejects with a TimeoutError
.
Related, since this touches the AbortSignal, there should probably be a test that asserts you can still pass an AbortSignal manually (i.e. Promise is rejected with AbortError
if signal is aborted).
Would work on it. Thanks
…On Tue, Oct 2, 2018 at 6:08 PM Felix Becker ***@***.***> wrote:
***@***.**** commented on this pull request.
Would it be possible to add a test for this? For example, if you set the
timeout 0 or 1ms then wait, you should be able to assert that the Promise
rejects with a TimeoutError.
Related, since this touches the AbortSignal, there should probably be a
test that asserts you can still pass an AbortSignal manually (i.e. Promise
is rejected with AbortError if signal is aborted).
------------------------------
In index.js
<#27 (comment)>:
> @@ -77,10 +78,13 @@ class TimeoutError extends Error {
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
-const timeout = (promise, ms) => Promise.race([
+const timeout = (promise, ms, abortController) => Promise.race([
I don't see this parameter being passed anywhere?
***@***.***/-/blob/index.js#L81:7&tab=references
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#27 (review)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AUBOX02JhV8vcwx2EIFoYhd54gdByXZ2ks5ug52qgaJpZM4Wh_JX>
.
--
Ashinze Ekene
|
I'm finding it hard to test. I initially had to installed browser-env to be able to use |
You shouldn't need a whole browser env with DOM etc for this. https://www.npmjs.com/package/abort-controller should be enough. |
@felixfbecker I don't think It sounds like support for testing in a real browser will land in ky soon: #34 |
Cool @ianwalter I found out that the tests I wrote kept failing. Didn't know if the issue was from node-fetch or abort-controller polyfill. Was about raising the issue here |
index.js
Outdated
promise, | ||
(async () => { | ||
await delay(ms); | ||
if (supportsAbortController) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (abortController) {
LGTM, we need only to test it on browsers somehow... |
@ianwalter I've added browser testing, so you can use this now to implement the tests: f0b7502 :) |
index.js
Outdated
if (supportsAbortController) { | ||
abortController = new AbortController(); | ||
if (this._options.signal) { | ||
this._options.signal.addEventListener('abort', () => abortController.abort()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This listener would need to be removed once the request is completed (or errored, i.e. in a finally
), otherwise the listener would leak and stay around forever even though the request cannot be cancelled anymore
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Believe it or not, this was never fixed before the PR landed so we ran into this memory leak in our application in 2024! 😄
index.js
Outdated
let abortController; | ||
|
||
if (supportsAbortController) { | ||
abortController = new AbortController(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
abortController
should be defined as a const
, since it's never re-assigned.
@ianwalter Still interested in finishing this? :) |
I've pushed fixes for this. But I need help writing tests. Can someone assist? |
I tried writing a test for this but doesn't look like headless cypress run (electron 59) supports AbortController. Will play around with it some more. Update: Not 100% sure but seems like we should just wait for Cypress v4 which will upgrade Electron to v3 which will use Chrome v66 which will add support for this. I read that you can't polyfill AbortController with native fetch so that's doesn't seem like an option. Other option is to use puppeteer/karmatic instead of Cypress. |
Yeah if you just assign AbortController on window fetch will not honor the signal. Do the test make actual HTTP requests though? What if fetch is also polyfilled (the polyfill can use xhr under the hood which is cancellable)? |
When I tried writing tests for it, I found out that it actually works. it('throws abort error when aborted by user', async () => {
const abortController = new AbortController();
const {signal} = abortController;
const response = ky('https://jsonplaceholder.typicode.com/todos', {signal});
expect(() => abortController.abort()).to.throw(window.AbortError);
await response;
}); The test fails but the request is actually aborted and I can't find a way to catch the error However, if i write the test this way it('throws abort error when aborted by user', async () => {
const abortController = new AbortController();
const {signal} = abortController;
const response = ky('https://jsonplaceholder.typicode.com/todos', {signal});
expect(abortController.abort).to.throw(window.AbortError);
await response;
}); |
I would have had a couple of working tests thanks to @felixfbecker's suggestion but forgot that using fetch (even if it's a polyfill) doesn't work with |
@ianwalter have you seen this example of stubbing |
Since when does window.AbortError exist? |
Sorry, there's actually nothing like that. I got the error from Cypress. I think its a type of DOMException |
error.name will be AbortError |
Bump :) |
Thank you, @ashinzekene :) |
Fixes #8