2

I have some code that basically calls fetch in Javascript. The third party services sometimes take too long to return a response and in an attempt to be more user-friendly, I want to be able to either post a message or stop the connection from being open after N milliseconds.

I had recently come across this post: Skip the function if executing time too long. JavaScript

But did not have much luck and had issues getting it to work with the below code. I was also hoping that there was a more modern approach to do such a task, maybe using async/await?

module.exports = (url, { ...options } = {}) => {
  return fetch(url, {
    ...options
  })
}
John Lippson
  • 1,269
  • 5
  • 17
  • 36
  • Does this answer your question? [How do I cancel an HTTP fetch() request?](https://stackoverflow.com/questions/31061838/how-do-i-cancel-an-http-fetch-request) – Titus Jan 09 '20 at 16:17
  • @Titus Is there ability to add abort after a certain amount of time has elapsed? – John Lippson Jan 09 '20 at 16:37

2 Answers2

4

You can use a combination of Promise.race and AbortController, here is an example:

function get(url, timeout) {
  const controller = new AbortController();
  return Promise.race([fetch(url, {
    signal: controller.signal
  }), new Promise(resolve => {
    setTimeout(() => {
      resolve("request was not fulfilled in time");
      controller.abort();
    }, timeout)
  })]);
}

(async() => {
  const result = await get("https://example.com", 1);
  console.log(result);
})();
Titus
  • 22,031
  • 1
  • 23
  • 33
  • This looks promising. My fetch client wrapper file is used by both front and backend. Is there some specific way I can get cross-compatability between those environments? Currently I'm seeing AbortController is not defined – John Lippson Jan 09 '20 at 17:02
  • @JohnLippson Try to add the [abort-controller](https://www.npmjs.com/package/abort-controller) module. – Titus Jan 09 '20 at 17:03
  • Should I be conditionally importing the module if window is or isn't defined since the file is shared between client/server? – John Lippson Jan 09 '20 at 17:05
  • @JohnLippson Do that if you can, if you can't, import it as some other name and set `window.AbortController` to it if it is `undefined`, eg: `window.AbortController = window.AbortController || importedAbortController`. – Titus Jan 09 '20 at 17:08
  • Hm, still no luck for some reason. Tried the following: let AbortController if (typeof window !== 'undefined') { AbortController = window.AbortController } else { AbortController = import('abort-controller').then(module => module).default } – John Lippson Jan 09 '20 at 17:16
  • @JohnLippson It should be something like `import('abort-controller').then(module => self.AbortController = module.default())` and you will have to wait for the promise to be fulfilled. – Titus Jan 09 '20 at 17:23
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/205693/discussion-between-john-lippson-and-titus). – John Lippson Jan 09 '20 at 17:30
  • This is great, but my follow up Q is we should probably be cleaning up the timeout as well right? It's a bit bit more convenient using the `async`/`await` syntax because you can store the timeoutID and clear it before returning the resolved value. Happy to contribute the edit if you agree @Titus – JaKXz Feb 02 '21 at 21:56
0

The native Fetch API doesn't have a timeout built in like something like axios does, but you can always create a wrapper function that wraps the fetch call to implement this.

Here is an example:

const fetchWithTimeout = (timeout, fetchConfig) => {
    const FETCH_TIMEOUT = timeout || 5000;
    let didTimeOut = false;
    return new Promise(function(resolve, reject) {
        const timeout = setTimeout(function() {
            didTimeOut = true;
            reject(new Error('Request timed out'));
        }, FETCH_TIMEOUT);

        fetch('url', fetchConfig)
        .then(function(response) {
            // cleanup timeout
            clearTimeout(timeout);
            if(!didTimeOut) {
                // fetch request was good
                resolve(response);
            }
        })
        .catch(function(err) {
            // Rejection already happened with setTimeout
            if(didTimeOut) return;
            // Reject with error
            reject(err);
        });
    })
    .then(function() {
        // Request success and no timeout
    })
    .catch(function(err) {
        //error
    });
}

from here https://davidwalsh.name/fetch-timeout

Travis James
  • 1,879
  • 9
  • 22