Arsalan S.
Arsalan S.

Reputation: 455

Doing a Timeout Error with Fetch - React Native

I have a user login function that is working. But, I want to incorporate a time out error for the fetch. Is there a way to set up a timer for 5 seconds or so that would stop trying to fetch after such a time? Otherwise, I just get a red screen after a while saying network error.

_userLogin() {
  var value = this.refs.form.getValue();
  if (value) {
    // if validation fails, value will be null
    if (!this.validateEmail(value.email)) {
      // eslint-disable-next-line no-undef
      Alert.alert('Enter a valid email');
    } else {
      fetch('http://51.64.34.134:5000/api/login', {
        method: 'POST',
        headers: {
          Accept: 'application/json',
          'Content-Type': 'application/json',
        },
        timeout: 5000,
        body: JSON.stringify({
          username: value.email,
          password: value.password,
        }),
      })
        .then((response) => response.json())
        .then((responseData) => {
          if (responseData.status == 'success') {
            this._onValueChange(STORAGE_KEY, responseData.data.token);
            Alert.alert('Login Success!');
            this.props.navigator.push({name: 'StartScreen'});
          } else if (responseData.status == 'error') {
            Alert.alert('Login Error', responseData.message);
          }
        })
        .done();
    }
  }
}

Upvotes: 17

Views: 39811

Answers (7)

Sourabh Gera
Sourabh Gera

Reputation: 1006

  let controller = new AbortController()
      
      setTimeout( () => {
          controller.abort()
      }, 10000);    // 10,000 means 10 seconds

    return fetch(url, {
        method: 'POST',        
        headers: headers, 
        body: JSON.stringify(param),
        signal: controller.signal
    })

enter image description here

enter image description here

Upvotes: 2

Fanchen Bao
Fanchen Bao

Reputation: 4289

// Wrapper function for fetch
const fetchSomething = async () => {
    let controller = new AbortController()
    setTimeout(() => controller.abort(), 3000);  // abort after 3 seconds
    const resp = await fetch('some url', {signal: controller.signal});
    const json = await resp.json();
    if (!resp.ok) {
        throw new Error(`HTTP error! status: ${resp.status}`);
    }
    return json;
}


// usage
try {
    let jsonResp = await fetchSomthing();
    console.log(jsonResp);
} catch (error) {
    if (error.name === 'AbortError') {
        console.log('Network Error');
    } else {
        console.log(error.message);
    }
}

I think using AbortController is the recommended way to abort a fetch call. The code snippet above handles the following scenarios:

  1. If network is good but HTTP returns an error status, the message "HTTP error! ..." will be logged.
  2. If network is down, setTimeout would trigger the AbortController to abort fetch after three seconds. The message "Network Error" will be logged.
  3. If network is good and HTTP response is good, the response JSON will be logged.

The documentation for using AbortController to abort fetch is here.

Upvotes: 9

WiRa
WiRa

Reputation: 998

I solved this problem by using a race between 2 promises, written as a wrapper around fetch. In my case I expect the request to return json so also added that. Maybe there is a better solution, but this works correctly for me!

The wrapper returns a promise which will resolve as long as there are no code errors. You can check the result.status for 'success' and read json data from result.data. In case of error you can read the exact error in result.data, and display it or log it somewhere. This way you always know what went wrong!

var yourFetchWrapperFunction = function (
  method,
  url,
  headers,
  body,
  timeout = 5000,
) {
  var timeoutPromise = new Promise(function (resolve, reject) {
    setTimeout(resolve, timeout, {
      status: 'error',
      code: 666,
      data:
        'Verbinding met de cloud kon niet tot stand gebracht worden: Timeout.',
    });
  });
  return Promise.race([
    timeoutPromise,
    fetch(connectionType + '://' + url, {
      method: method,
      headers: headers,
      body: body,
    }),
  ])
    .then(
      (result) => {
        var Status = result.status;
        return result
          .json()
          .then(
            function (data) {
              if (Status === 200 || Status === 0) {
                return {status: 'success', code: Status, data: data};
              } else {
                return {
                  status: 'error',
                  code: Status,
                  data: 'Error (' + data.status_code + '): ' + data.message,
                };
              }
            },
            function (response) {
              return {
                status: 'error',
                code: Status,
                data: 'json promise failed' + response,
              };
            },
          )
          .catch((error) => {
            return {status: 'error', code: 666, data: 'no json response'};
          });
      },
      function (error) {
        return {status: 'error', code: 666, data: 'connection timed out'};
      },
    )
    .catch((error) => {
      return {status: 'error', code: 666, data: 'connection timed out'};
    });
};

Upvotes: 1

Roberto Rodriguez
Roberto Rodriguez

Reputation: 3347

This is what I did to go around it: (This is the "generic" function I use to make all calls on my app)

I created a timeout function, that will be triggered unless it is cleared before, then I clear this timeout on server response

const doFetch = (url, callback, data) => {
  //... creating config obj here (not relevant for this answer)
  var wasServerTimeout = false;
  var timeout = setTimeout(() => {
    wasServerTimeout = true;
    alert('Time Out');
  }, 3000);
  fetch(HOST + url, config)
    .then((response) => {
      timeout && clearTimeout(timeout); //If everything is ok, clear the timeout
      if (!wasServerTimeout) {
        return response.json();
      }
    })
    .then((response) => {
      callback && callback(response.data || response);
    })
    .catch((err) => {
      //If something goes wrong, clear the timeout
      timeout && clearTimeout(timeout);
      if (!wasServerTimeout) {
        //Error logic here
      }
    });
};

Upvotes: 2

Rishav Kumar
Rishav Kumar

Reputation: 5460

I may be late but i made a code which is 100% working to timeout an API request using fetch.

fetch_timeout(url, options) {
  let timeout = 1000;
  let timeout_err = {
    ok: false,
    status: 408,
  };
  return new Promise(function (resolve, reject) {
    fetch(url, options)
      .then(resolve, reject)
      .catch(() => {
        alert('timeout.');
      });
    setTimeout(reject.bind(null, timeout_err), timeout);
  });
}

You just need to pass the api-endpoint to the url and body to the options parameter.

Upvotes: 0

Cassio Seffrin
Cassio Seffrin

Reputation: 8570

I have made a ES6 function that wraps ES fetch into a promise, here it is:

export async function fetchWithTimeout(url, options, timeout = 5000) {
    return Promise.race([
        fetch(url, options),
        new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeout))
    ]);
}

Here is how to use it:

const requestInfo = {
    method,
    headers,
    body,
};
const url = 'http://yoururl.edu.br'
let data = await fetchWithTimeout(url, requestInfo, 3000);

Upvotes: 15

Michael Cheng
Michael Cheng

Reputation: 10471

There is no standard way of handling this as a timeout option isn't defined in the official spec yet. There is an abort defined which you can use in conjunction with your own timeout and Promises. For example as seen here and here. I've copied the example code, but haven't tested it myself yet.

// Rough implementation. Untested.
function timeout(ms, promise) {
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      reject(new Error("timeout"))
    }, ms)
    promise.then(resolve, reject)
  })
}

timeout(1000, fetch('/hello')).then(function(response) {
  // process response
}).catch(function(error) {
  // might be a timeout error
})

Another option would be to modify the fetch.js module yourself to add a timeout that calls abort as seen here.

Upvotes: 3

Related Questions