【问题标题】:Doing a Timeout Error with Fetch - React Native使用 Fetch 执行超时错误 - React Native
【发布时间】:2017-02-09 22:03:25
【问题描述】:

我有一个正在运行的用户登录功能。但是,我想为获取合并一个超时错误。有没有办法设置一个 5 秒左右的计时器,这样在这段时间之后就会停止尝试获取?否则,我只是在说网络错误后出现红屏。

_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();
    }
  }
}

【问题讨论】:

    标签: react-native fetch


    【解决方案1】:

    我做了一个 ES6 函数,将 ES fetch 包装成一个 Promise,这里是:

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

    这里是如何使用它:

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

    【讨论】:

    • 简单说明一下,Android 端似乎存在问题,Promise.race 无法正常工作。
    • @MikeHuebner 我在 react-native: 0.61.2 的 android 和 iOS 生产中使用这种方法。在这种环境下,一切正常。愿你能发送你的风景和你的改编。
    • 当然,当我在 iOS 上时,我最终不得不做一些简单的事情来做一个竞态条件。 Android 场景无法正确获取,反而会失败。我无法弄清楚为什么 promise.race 在两个平台上都可以正常工作。您是偶然使用 Hermes 吗?
    • 不,我没有。我使用默认的 JS 引擎 JavaScriptCore。我做了一个抽象提取,所以我所有的请求都通过这个方法。我可以向您保证,我将此脚本用于至少 3 个应用程序(商店中的 ios/android)。对我来说,我从来没有任何例外。我认为你有一些环境问题。我很想知道您在 android 上的例外情况。
    【解决方案2】:
    // 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);
        }
    }
    

    我认为使用AbortController 是中止fetch 呼叫的推荐方法。上面的代码sn-p处理了以下场景:

    1. 如果网络良好但 HTTP 返回错误状态,则会记录消息“HTTP 错误!...”。
    2. 如果网络中断,setTimeout 将触发AbortController 在三秒后中止fetch。将记录“网络错误”消息。
    3. 如果网络良好且 HTTP 响应良好,则会记录响应 JSON。

    使用AbortController 中止fetch 的文档是here

    【讨论】:

    • 请求完成后是否应该取消超时? clearTimeout(abortTimeout);
    • 我在使用上面的代码sn-p的时候没有取消超时,而且似乎按预期工作。我怀疑如果 fetch 完成超时,超时会自动取消。
    【解决方案3】:

    没有标准的方法来处理这个a timeout option isn't defined in the official spec yetThere is an abort defined 可以与您自己的超时和承诺结合使用。例如herehere。我已经复制了示例代码,但还没有自己测试过。

    // 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
    })
    

    另一种选择是自己修改 fetch.js 模块,以添加调用 abort 的超时,如 here 所示。

    【讨论】:

    • 这样做只能解决问题的表面,在一定时间后无法中止请求。
    • @TomSawyer 但这不是被问到的;该问题不要求在超时后处理中止。此外,我的回答确实包括指向可能的中止解决方案的链接,以防其他人希望朝着这个方向发展。所以我不太确定你的评论是什么意思?
    • 我为那些关心真正的超时机制如何工作的人指出。在http请求中,一个连接超时意味着中止连接,它分配的内存需要释放,它的进程需要终止,不仅为ui更新返回false,然后继续在后台运行。
    • 如果你看过你链接的一个链接,发帖人名字mislav也说:Note that this is not a connection timeout. This is a response timeout. Due to technical restrictions we can't implement a connection timeout. Also note that with the above implementation, even if the timeout happens, the original request won't be aborted because we don't have an API to abort fetch requests. Instead, the request will complete in the background but its response will get discarded.
    • @TomSawyer 是的......这就是我在回答中写第一句话的原因,我链接这些资源的原因,以及我说关于中止的部分的原因。答案是对当前可用选择的总结。所以......我还是不明白你为什么要制作这些cmets?如果您认为我的答案不够充分,或者您可以发布更好的答案,那就这样做。这就是 StackOverflow 的重点。
    【解决方案4】:

    这就是我绕过它所做的: (这是我用来在我的应用上进行所有调用的“通用”函数)

    我创建了一个超时函数,除非之前被清除,否则会触发它,然后我在服务器响应时清除这个超时

    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
          }
        });
    };
    

    【讨论】:

    • 所以无论如何你得到它:/但如果在 3 秒内没有得到它,你会发出警报。
    【解决方案5】:

    我通过使用 2 个 Promise 之间的竞争解决了这个问题,编写为 fetch 的包装器。在我的情况下,我希望请求返回 json 所以也添加了。也许有更好的解决方案,但这对我来说是正确的!

    包装器返回一个承诺,只要没有代码错误,它就会解决。 您可以检查 result.status 的“成功”并从 result.data 中读取 json 数据。如果出现错误,您可以在 result.data 中读取确切的错误,并将其显示或记录在某处。这样你总能知道哪里出了问题!

    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'};
        });
    };
    

    【讨论】:

    • 这是一个很好的解决方案,但是提取不会被取消,例如上传数据时会出现问题
    • 确实是这样,但连接速度慢总是a**中的p***!我尝试通过在服务器上拆分和重新加入来最小化上传数据的大小,并在一部分失败时停止上传。稍后您可以使用剩余的文件部分重试...这帮助我解决了 wifi/4g 连接不良的问题。
    【解决方案6】:

    我可能迟到了,但我编写了一个 100% 使用 fetch 超时 API 请求的代码。

    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);
      });
    }
    

    您只需将api-endpoint 传递给url 并将body 传递给options 参数。

    【讨论】:

      猜你喜欢
      • 2017-07-27
      • 2018-08-24
      • 2017-02-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-07-24
      • 2020-07-27
      相关资源
      最近更新 更多