4

I'm trying to get some data from a server with fetch request. Some times network will fail or similar other things happens and I want to have a timeout to prevent further errors and also have a better experience.

Actually I want to wait for 20 seconds and if I don't get any response I want to show an error and also break the fetch request.

I have a loading modal which I can close it by timeout but I want to break the fetch request either.

here is my fetch request code:

_testPress = async () => { 
        //setTimeout(() => {this.setState({loading: false})}, 20000)
        this.setState({loading : true})
        fetch(getInitUrl('loginUser'), {
        method: 'POST',
        headers: {
          Accept: 'application/json',
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({          
          password : this.state.password,
          email : this.state.emailAddress,            
        }),
      }).then(response => Promise.all([response.ok, response.status ,response.json()]))
      .then(([responseOk,responseStatus, body]) => {        
        if (responseOk) {
          //console.log(responseOk, body);
          this._signInAsync(body.token);
          // handle success case
        } else {
          console.log(responseStatus);
          this.setState({
            showAlert : true,
            alertType : true,
            alertMessage : body.message
          });
        }
      })
      .catch(error => {
        console.error(error);
        // catches error case and if fetch itself rejects
      });
      }

I used setTimeout to close loading module but it wont stop actual request which I want it to stop after 20 seconds.

Please help me with your advice. thx.

Pouya92
  • 453
  • 1
  • 8
  • 18
  • Possible duplicate of [Fetch API request timeout?](https://stackoverflow.com/questions/46946380/fetch-api-request-timeout) – Tyro Hunter Apr 09 '19 at 12:45

3 Answers3

2

There is no standard param you can add to fetch, but you can do this work around:

// creating a wrapper for promises
function timeout(milliseconds, promise) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(new Error("timeout exceeded"))
    }, milliseconds)
    promise.then(resolve, reject)
  })
}

// using that wrapper with fetch
timeout(1000, fetch('/api'))
  .then(function(response) {
     // response success
}).catch(function(error) {
  // timeout error or server error
})

EXAMPLES:

Timeout exceeded:

// creating a wrapper for promises
function timeout(milliseconds, promise) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject(new Error("timeout exceeded"))
        }, milliseconds);

        promise.then(resolve, reject);
    });
}
  
const requestErr = new Promise((resolve, reject) => {
    setTimeout(() => {
        // request finished.
        resolve();
    }, 2500);
})

timeout(1000, requestErr)
    .then(function(response) {
        console.log("OK!");
    }).catch(function(error) {
        console.log("ERROR TIMEOUT!");
    });

Timeout not exceeded:

// creating a wrapper for promises
function timeout(milliseconds, promise) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject(new Error("timeout exceeded"))
        }, milliseconds);

        promise.then(resolve, reject);
    });
}

const requestOk = new Promise((resolve, reject) => {
    setTimeout(() => {
        // request finished.
        resolve();
    }, 500);
})

timeout(1000, requestOk)
    .then(function(response) {
        console.log("OK!");
    }).catch(function(error) {
        console.log("ERROR TIMEOUT!");
    });

You can also use AXIOS that has its own timeout setting.

Dave
  • 1,912
  • 4
  • 16
  • 34
2

You can abort fetch request by passing "signal" into fetch options.

An AbortSignal object instance; allows you to communicate with a fetch request and abort it if desired via an AbortController.

https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch

Full code example is here.

https://javascript.info/fetch-abort

let controller = new AbortController();
fetch(url, {
  signal: controller.signal
});

controller.abort();
1

Fetch doesn't implement a connection timeout. You can't abort a fetch request either.

Take a look at this gist, which wraps a Promise.race around the fetch request. The promise will resolve as soon as one of the promises (the fetch or the timeout) resolves or rejects:
https://gist.github.com/davej/728b20518632d97eef1e5a13bf0d05c7

Something like this should work:

Promise.race([
  fetch(getInitUrl('loginUser'), *...fetchparams* ), 
  new Promise((_, reject) =>
    setTimeout(() => reject(new Error('Timeout')), 7000)
  )
]).then(*stuff*).catch(*stuff*)

Alternatively, you can also give axios a try, an alternative to fetch, with support for timeouts: https://github.com/axios/axios

brkn
  • 646
  • 5
  • 11
  • I tried your suggestion and it worked. But as I mentioned I already used setTimeout and It did work, but it didn't stop fetch request. and your solution also didn't stop that request. so could you please tell me the difference of your solution and a simple setTimeout(() => {this.setState({loading: false})}, 20000) in my function? – Pouya92 Apr 09 '19 at 14:21
  • 1
    Unfortunately, you can't stop a fetch request. If you really need to abort your request after a time out, you'll have to take a look at another HTTP library (like axios). In my example with `Promise.race`, the request still goes through. However, it shouldn't throw an error anymore if it fails later, if I'm not mistaken. – brkn Apr 09 '19 at 15:25
  • can I use axios for react native? – Pouya92 Apr 09 '19 at 18:13