4

The Promise API doesn't have a chainable timeout option, but in Steve Sanderson's presentation at NDC Conference (at 15:31 here https://www.youtube.com/watch?v=9G8HEDI3K6s&feature=youtu.be&t=15m31s), he presented an elegant chainable timeout on the fetch API. It looks like this:

enter image description here

The great thing about this approach is that the resolve handler still completed (e.g. the response was still put into cache) even after the timeout. He demo'd this during his presentation (and available at the YouTube link above). Anyone know how this chainable timeout was implemented?

Johnny Oshika
  • 54,741
  • 40
  • 181
  • 275
  • There's about a dozen helpful links when you google 'fetch timeout javascript' such as [this](https://stackoverflow.com/questions/46946380/fetch-api-request-timeout) – Sterling Archer Mar 26 '18 at 01:35
  • 2
    This is cool. Never knew about this. Sterling, take it easy on the sarcasm. After all, we’re all here to help each other out. – iCode Mar 26 '18 at 01:43
  • 1
    Not so "helpful" if they don't implement this `timeout` method on a promise as shown in the code :p – Jaromanda X Mar 26 '18 at 01:43
  • This looks more like an rsjx observable is being returned than a promise. – Mark Mar 26 '18 at 01:53
  • @SterlingArcher the link you pointed to does not chain a `timeout`. Also, the resolve handler doesn't get called when there's a timeout (I tested it :-)). I've looked long and hard for an answer to my question with no success, but you may be a better Googler than I am, so if you can find one, I'd appreciate it if you can share it. Just put it in the answer box. I promise I'll give you full credit for it. :-) – Johnny Oshika Mar 26 '18 at 03:08
  • It isn't clear what a `timeout(200)` in that position of the chain should even do. It seems like the wrong spot in the chain because it can't mess with the original promise from there so it doesn't affect whether the first `.then()` gets called or not. It's trivial to make `fetchWithTimeout()` yourself that you just call instead of `fetch()` if that would work for you. – jfriend00 Mar 26 '18 at 03:13
  • @jfriend00 In Steve Sanderson's demo, the `timeout(200)` causes the `catch` hander to be called while the resolve handler is still called. It's pretty impressive and his demo is a great use case for such a behavior. – Johnny Oshika Mar 26 '18 at 03:15
  • 2
    I understand. But, I can't think of a single other use for it than this type of caching where you want both a timeout error and the original `.then()` handler to get called. Doesn't seem generally useful to me at all. The generally useful case is where you'd have a timeout on the `fetch()` itself and you get either the successful data or an error (one possible error would be the timeout). That one I could offer to you easily in an answer if you want it - not the one pictured in your question. That requires hacking the built-in promises or hacking `fetch()` itself, both of which have issues. – jfriend00 Mar 26 '18 at 03:17
  • @jfriend00 Interesting. This does seem to go against the idea of a promise, which is to resolve or reject and not both. I'd still like to know how Steve Sanderson did this, but as you say, it may come with problematic side-effects. – Johnny Oshika Mar 26 '18 at 03:23
  • So if the response is slow then promise provides us with a previopusly cached data and whenever or if ever the promise resolves it does it's job. HOw about if it rejects..? This is not the job of a promise. With available tools you can always simply implement this functionality. – Redu Mar 26 '18 at 09:20
  • Can't you do a `Promise.race([fetch.then(cache),rejectLater(timeout)]).catch(fromCache)` where `rejectLater(timeout)` will reject after timeout. – HMR Mar 26 '18 at 09:53
  • See [Promise - is it possible to force cancel a promise](https://stackoverflow.com/questions/30233302/promise-is-it-possible-to-force-cancel-a-promise) – Ronnie Royston Mar 26 '18 at 23:36

1 Answers1

1

I usually use Promise.race to implement timeouts. Normally I haven't tried to make this chainable, but that's a cool idea so I'll give it a go in a moment.

This is how I'd normally use it to implement a timeout:

function timeout (promise, duration) {
    return Promise.race([
        promise,
        new Promise((resolve, reject) => {
            setTimeout(
                () => reject(new Error("Timeout")), 
                duration
            )
        })
    ]);
} 

timeout(fetch("something"), 5000)
    .then(() => {
        // ... Operation completed without timeout ...
    })
    .catch(err => {
        // ... An error occurred possibly a timeout ...
    ));

You might be able to make this chainable by attaching your function to the prototype of the Promise class (maybe depending on which promise library you are actually using):

Promise.prototype.timeout = function (duration) {
    return Promise.race([
        this,
        new Promise((resolve, reject) => {
            setTimeout(
                () => reject(new Error("Timeout")), 
                duration
            )
        })
    ]);
};

fetch("something")
    .then(() => ...) // This executes before the timeout.
    .timeout(5000)
    .catch(err => ...);
Ashley Davis
  • 9,896
  • 7
  • 69
  • 87