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 timeout option #20

Open
wheresrhys opened this Issue Jan 15, 2015 · 68 comments

Comments

@wheresrhys
Copy link

wheresrhys commented Jan 15, 2015

Most ajax libraries and xhr itself offer a convenient way to set a timeout for a request. Adding timeout to the properties accepted in the options object therefore seems like a sensible addition to fetch.

github/fetch#68

@domenic

This comment has been minimized.

Copy link
Member

domenic commented Jan 15, 2015

Ideally we would have a design for cancelable promises that fetch could use. But unfortunately there are still four or five competing proposals thrown out and every time a web developer asks me "why can't we do cancelable promises?" and I look into exactly what they're asking for I end up adding a new proposal to that list. It would take some concerted effort to try to find something good.

In the meantime I think a sensible cancelation strategy is to reject the promise with some sort of error. The actual cancel() method if it exists should not be on the returned promise though, at least not until we figure out cancelable promises. That leaves a couple options I can see:

  • Don't let people cancel requests until the headers come back and you have a response object. In this case you could use the built in stream cancelation of ReadableStream: res.body.cancel().
  • Come up with something more awkward. E.g., fetch.cancelable(...) returns a { promise, cancel } tuple. Not horrible with destructuring but pretty awkward. And will likely be obsoleted by cancelable promises.

As for the error itself we could future-proof there slightly better. If we create a new Error subclass called e.g. Cancellation that follows all the normal ES conventions for Error subclasses, in the worst case it ends up being fetch-only but in the best case ES ends up using it for cancellable promises.

@matthew-andrews

This comment has been minimized.

Copy link

matthew-andrews commented Jan 15, 2015

Don't let people cancel requests until the headers come back and you have a response object. In this case you could use the built in stream cancelation of ReadableStream: res.body.cancel().

This doesn't feel particularly useful… the cases where you would want a fetch to timeout would be when the network conditions were not good and you weren't getting anything back…

Given timeout is such a common use case, even if promises had a concept of being cancelled providing a timeout option seems immensely useful… what am I not seeing here?

@kornelski

This comment has been minimized.

Copy link

kornelski commented Jan 15, 2015

To me it seems that having API to cancel started fetch is completely separate from request timeouts. Mechanism used for cancellation on timeout can be completely private (for now).

@annevk

This comment has been minimized.

Copy link
Member

annevk commented Jan 15, 2015

@pornel yes, perhaps. If we expose termination I would expect it to be on Request myself, as we also plan to expose Request elsewhere, on elements and such.

@annevk

This comment has been minimized.

Copy link
Member

annevk commented Jan 15, 2015

@matthew-andrews baby steps. It'll come.

@wheresrhys

This comment has been minimized.

Copy link
Author

wheresrhys commented Jan 16, 2015

Ideally we would have a design for cancelable promises that fetch could use.

What are the reasons that rejection of an ordinary promise would be an unacceptable solution?@domenic

@annevk

This comment has been minimized.

Copy link
Member

annevk commented Jan 16, 2015

How do you envision that would work?

@wheresrhys

This comment has been minimized.

Copy link
Author

wheresrhys commented Jan 16, 2015

Assuming that fetch creates the promise, then can't it just have a setTimeout* within the executor function which calls reject? Why is that not feasible?

In the meantime I think a sensible cancelation strategy is to reject the promise with some sort of error.

- @domenic

Why is that not good enough to run with?

*or equivalent in whichever language the browser implements it in

@wanderview

This comment has been minimized.

Copy link
Member

wanderview commented Jan 16, 2015

If this is spec'd, please also define behavior for timeouts occurring after headers are received, but before the end of the content body.

@Jxck

This comment has been minimized.

Copy link

Jxck commented Feb 10, 2015

same question here, I wanna abort() request.

I think what we need is abort(). timeout is enable using setTimeout + abort.
and in the fetch spec, I think the problem is fetch doesn't has a instance, only a function.
fetching process needs a internal state, and will resolve at endpoint state, reject otherwise.

in my opinion will like this.

var req = new Request(url, option);
req.fetch().then((res) => {
  console.log(res.status);
}).catch((err) => {
  console.error(err); // this will also happen in `abort()` request
});

// timeout a request
setTimeout(function() {
  req.abort(); // reject the fetching process
}, 1000);
@annevk

This comment has been minimized.

Copy link
Member

annevk commented Feb 10, 2015

I agree that we want something like that. It's a bit unclear to me still what the exact semantics should be.

@matthew-andrews

This comment has been minimized.

Copy link

matthew-andrews commented Feb 10, 2015

As one possible option, node-fetch has implemented timeouthttps://github.com/bitinn/node-fetch#options — might be worth looking at/considering… it's simple 😄

@annevk

This comment has been minimized.

Copy link
Member

annevk commented Feb 10, 2015

The semantics are still unclear. Does the timeout run until the promise returns? Does the timeout terminate the underlying stream somehow once the promise has returned?

@Jxck

This comment has been minimized.

Copy link

Jxck commented Feb 10, 2015

@matthew-andrews I think timeout should be Hi-level API on abort().

@annevk I think node.js http module will help us including make res as streams for getting progress event.
http://nodejs.org/api/http.html#http_http_request_options_callback

@kornelski

This comment has been minimized.

Copy link

kornelski commented Feb 10, 2015

There's an interesting difference between "deadline" and "timeout".

From man ping:

  • -w deadline
    Specify a timeout, in seconds, before ping exits regardless of how many packets have been sent or received. In this case ping does not stop after count packet are sent, it waits either for deadline expire or until count probes are answered or for some error
    notification from network.
  • -W timeout
    Time to wait for a response, in seconds. The option affects only timeout in absense of any responses, otherwise ping waits for two RTTs.

The "deadline" option is implementable using setTimeout(), but an actual timeout, when measured as time from the last packet from the server, is actually more interesting and useful, and something that only browser can implement. I'd definitely like timeout to mean the latter, because it can distinguish between connection that's dead and a working connection that's just slow.

@Jxck

This comment has been minimized.

Copy link

Jxck commented Feb 10, 2015

that's interesting but is that scope of fetching ?
network programming has tons of configurable options,
but adding much of them will make spec like a Camel.

in fetch context, I think abort should same behave on XHR,
because XHR could build upon fetch api.

in application, that will use for terminate the process gracefully,
and getting back flow control to user land javascript.
(ex, force reject promise, force call a callback)

If you wanna do something like that, raw-socket API may works.

@kornelski

This comment has been minimized.

Copy link

kornelski commented Feb 10, 2015

@Jxck Yes, I think timeout is in scope of fetching, and that's an important feature.

In the FT app we've had to add timeouts to every network request, because we've learned that on some networks requests just never finish, and that can make the app unusable. Now we're treating network requests without a timeout as a bug.

I'm not opposed to having an API to abort requests. I think these are largely independent problems: it's possible to have timeouts without a public abort API, and it's possible to have only abort API and leave timeout implementation to the user. I think having both would be the best.

If the decision was to only have an abort API instead of support for timeout, then that would be workable, but not ideal — I'd always have to wrap fetch in setTimeout myself, and perhaps add even more elaborate wrapper if I wanted to implement timeout from the last received bit of data, rather than a simple "deadline" option that could unnecessarily throw away data on slow connections.

@Jxck

This comment has been minimized.

Copy link

Jxck commented Feb 11, 2015

ah ok you talking about timeout option, not timeout() method.
that seems to add option to RequestInit https://fetch.spec.whatwg.org/#requestinit
(add or not is another discussion)

adding a option doesn't breaking changes of fetch(url, option).
but adding a programmable abort() or progress event may change interface.

in some browser start to implements fetch(), so I think we need to start discussion about
breaking changes immediately if we do. (or too late to change fetch api already?)

@annevk

This comment has been minimized.

Copy link
Member

annevk commented Feb 13, 2015

The problem with Request.prototype.abort() is that at the moment fetch() clones its input. That is, the current design is such that you can fetch() a single Request instance multiple times (unless it has a body).

(There is also some complication with Request being used as cache key and as a way to tell a service worker what a client (document/worker) wants.)

Would need to think a bit about how we can change some of these characteristics to make it all fit better.

@wojciak

This comment has been minimized.

Copy link

wojciak commented Feb 13, 2015

It depends how high of an API fetch should be. If it's a low-level request - we don't need abort. We'll wrap it in a higher level promise API and that's it.

If on the other hand it's high-level and we want to abort, it should be instantiated and work like a resource so: var someResource = new Fetch(); someResource.abort();

The first option imho is better.

@annevk

This comment has been minimized.

Copy link
Member

annevk commented Feb 17, 2015

Discussion for abort() moved to w3c/ServiceWorker#625 with a subclassed promise returned from fetch() being the leading candidate to expose it.

Leaving this open for the timeout idea from @pornel which seems like a rather interesting primitive to expose, network stacks allowing.

@Jxck

This comment has been minimized.

Copy link

Jxck commented Feb 26, 2015

doing fetch with just one function is difficult to expand with adding api in future.
(anti Open-Closed Principle)

in current interface, we do all thing with

  • adding new props to option arguments
  • adding new methods to returned promise

the problem is current fetch() is a high level api.
but I believe we need a low level api for fetching
what has capability for building XHR or kind of high-level api on top of it.

I heard that chrome are going to export fetch to window in M42 (mid April)
If that happens, it became hard to change fetch interface.
I think we need to discuss more about current api while we have a chance to change api.

@annevk

This comment has been minimized.

Copy link
Member

annevk commented Feb 26, 2015

What is wrong with fetch() as a low-level API? It seems to me that the amount of extensibility hooks we have is fine. We have the Request object for initialization, and at some point we'll have the FetchPromise object for status and control. What else is needed?

@annevk

This comment has been minimized.

Copy link
Member

annevk commented Feb 26, 2015

Also, I would appreciate it if you could move such a discussion to a new issue. Or perhaps we should move timeout to a new issue as this is getting rather cluttered.

@CamiloTerevinto

This comment has been minimized.

Copy link

CamiloTerevinto commented Aug 20, 2017

It's been well over an year since the last comment, is there any progress on any of this?

@annevk

This comment has been minimized.

Copy link
Member

annevk commented Aug 21, 2017

We're pretty close to fixing #447. That should make it a lot easier to implement this in a library and eventually get something standardized.

@jakearchibald

This comment has been minimized.

Copy link
Collaborator

jakearchibald commented Aug 21, 2017

Using the abort syntax, you'll be able to do:

const controller = new AbortController();
const signal = controller.signal;

const fetchPromise = fetch(url, {signal});

// 5 second timeout:
const timeoutId = setTimeout(() => controller.abort(), 5000);
const response = await fetchPromise;
//

If you only wanted to timeout the request, not the response, add:

clearTimeout(timeoutId);
//
@mattlubner

This comment has been minimized.

Copy link

mattlubner commented Aug 21, 2017

@jakearchibald Forgive me if I'm being obtuse, but how does clearing the timeout after the response resolves timeout only the request? If clearTimeout(timeoutId) is after await fetchPromise, it will only clear the timeout after the response is received. Unless I'm missing something?

@CamiloTerevinto

This comment has been minimized.

Copy link

CamiloTerevinto commented Aug 21, 2017

@annevk thanks, I did not see that issue.

@jakearchibald is there any timeline for that to be production-ready or are you implying I can use that already? I also have the same doubt as matlubner.

@jokeyrhyme

This comment has been minimized.

Copy link

jokeyrhyme commented Aug 21, 2017

@CamiloTerevinto @mattlubner I believe const response = await fetch(url) gives you a response object that is not necessarily "done", but provides you with body methods to process content that will eventually be available

const response = await fetch(url)
// if you abort here, then some downloading may have happened, but you can avoid the rest
const text = await response.text()
// if you abort here, then you just downloaded the whole thing, wasteful!
@annevk

This comment has been minimized.

Copy link
Member

annevk commented Aug 22, 2017

It gives you a response with headers, but without a response body. A response with headers is the earliest signal we're exposing in the web platform. (Note that it doesn't necessarily mean that the request body has been fully transmitted, though typically that's the case.)

@jakearchibald

This comment has been minimized.

Copy link
Collaborator

jakearchibald commented Aug 23, 2017

@CamiloTerevinto Edge & Firefox are already implementing. Chrome will start shortly. I don't know about Safari. Timelines for production-ready depend on the release schedule of individual browsers.

@CamiloTerevinto

This comment has been minimized.

Copy link

CamiloTerevinto commented Aug 23, 2017

@jakearchibald this might not be the right place, I'm aware, but I was looking into this for the react-native fetch implementation (which as far as I know, relies on this library).

@HyperSimon

This comment has been minimized.

Copy link

HyperSimon commented Nov 19, 2017

@domenic 因噎废食

cissp0 added a commit to cissp0/tabris-js that referenced this issue Nov 30, 2017

Add timeout option to fetch
There is a long discussion about a timeout option in the fetch spec [1].
Chances are that a property named `timout` will be included in the
config object.

Without this property, every reasonable app has to wrap the fetch
promise in a timeout wrapper. Tabris can provide a native timeout
implementation which is superior, as it actually aborts the running
request.

Proactively provide a this config property to make use of the native
timeout implementation.

[1] whatwg/fetch#20

Change-Id: I88059e9ff306e4c15196ba97bd92cba58a04aa77
@Angelk90

This comment has been minimized.

Copy link

Angelk90 commented Jan 17, 2018

@alcat2008 : Hello, I tried the code you proposed but it does not seem to work.
In a nutshell, the page to which I refer to a redirect, this is the problem.
Can you give me some advice?

@jasonwr

This comment has been minimized.

Copy link

jasonwr commented Apr 11, 2018

@Angelk90 et al, you might want to have a look at this AjaxService I wrote:

github/fetch#617

IMHO node-fetch gives you everything the native Fetch API within ES6 does and it provides some additional features including a timeout

@xgqfrms

This comment has been minimized.

Copy link

xgqfrms commented Jun 14, 2018

@FranklinYu

This comment has been minimized.

Copy link

FranklinYu commented Sep 9, 2018

@xgqfrms How on earth is that related to this issue?

@ImanMh

This comment has been minimized.

Copy link

ImanMh commented Dec 4, 2018

2018! do we have timeout?

@taralx

This comment has been minimized.

Copy link

taralx commented Dec 4, 2018

@FranklinYu

This comment has been minimized.

Copy link

FranklinYu commented Dec 4, 2018

According to Can I Use link above, Safari doesn't support AbortController, but MDN says otherwise: https://developer.mozilla.org//en-US/docs/Web/API/AbortController#Browser_compatibility

How can I verify this? I tried this JavaScript code:

const controller = new AbortController()
const signal = controller.signal

document.getElementById('download').addEventListener('click', fetchVideo)
document.getElementById('abort').addEventListener('click', () => {
    controller.abort()
    console.log('Download aborted')
})

// some random large image I found online
const url = 'https://upload.wikimedia.org/wikipedia/commons/6/6e/Monasterio_Khor_Virap%2C_Armenia%2C_2016-10-01%2C_DD_25.jpg'

function fetchVideo() {
    fetch(url, {signal})
        .then( r => r.blob() )
        .then( () => console.log('success') )
        .catch( e => console.error('Download error: ' + e.message) )
}

but my Internet is too fast, and the image finished in a second so I cannot hit "Abort" before that. Any other way to test it?

I wish Safari has the throttling like Chrome...

@jasonwr

This comment has been minimized.

Copy link

jasonwr commented Dec 4, 2018

@FranklinYu the easiest way to verify if it works is to run it in Safari. 💯

@fastest963

This comment has been minimized.

Copy link

fastest963 commented Dec 4, 2018

@FranklinYu you can use settimeout.io and just request a 5s timeout. For example: https://settimeout.io/5s

@styfle

This comment has been minimized.

Copy link
Contributor

styfle commented Dec 4, 2018

@FranklinYu It exists but it doesn't work.

Safari has window.AbortController defined in the DOM but it's just a stub, it does not abort requests at all. The same issue also affects Chrome on IOS and Firefox on IOS because they use the same WebKit rendering engine as Safari.

See caniuse

@jasonwr

This comment has been minimized.

Copy link

jasonwr commented Dec 4, 2018

@FranklinYu I believe that there are tools to slow down your connection with Safari else just go to a local coffee shop. For Safari a quick Google search reveals:

https://spin.atomicobject.com/2016/01/05/simulating-poor-network-connectivity-mac-osx/

Though that is from 2016. You'll have to try it out.

@FranklinYu

This comment has been minimized.

Copy link

FranklinYu commented Dec 4, 2018

@styfle I saw that note, but I would like to verify this myself before I submit pull request to https://github.com/mdn/browser-compat-data.

@fastest963's suggestion is right on the spot. I'm going to fix the compatibility table.

update

Damn, I'm an idiot. W3C officially provide powerful test page.

https://w3c-test.org/fetch/api/abort/general.any.html

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment