获取 API 请求超时?

IT技术 javascript ajax fetch-api
2021-01-12 04:50:14

我有一个fetch-api POST要求:

fetch(url, {
  method: 'POST',
  body: formData,
  credentials: 'include'
})

我想知道这个的默认超时时间是多少?我们如何将其设置为特定值,例如 3 秒或无限秒?

6个回答

使用Promise竞争解决方案将使请求挂起并仍然在后台消耗带宽,并在它仍在处理时降低最大允许并发请求。

而是使用AbortController来实际中止请求,这是一个示例

const controller = new AbortController()

// 5 second timeout:
const timeoutId = setTimeout(() => controller.abort(), 5000)

fetch(url, { signal: controller.signal }).then(response => {
  // completed request before timeout fired

  // If you only wanted to timeout the request, not the response, add:
  // clearTimeout(timeoutId)
})

AbortController 也可以用于其他事情,不仅可以用于获取,还可以用于可读/可写流。更多较新的函数(特别是基于 Promise 的函数)将越来越多地使用它。NodeJS 也在其流/文件系统中实现了 AbortController。我知道网络蓝牙也在研究它。现在它也可以与 addEventListener 选项一起使用,并在信号结束时停止监听

这看起来比 promise-race-solution 更好,因为它可能会中止请求,而不是只接受较早的响应。如我错了请纠正我。
2021-03-13 04:50:14
有这个答案总比没有答案好,因为人们被挑剔的人推迟了,tbh
2021-03-15 04:50:14
它可能无法解释 AbortController 是什么(我添加了一个指向答案的链接以使懒惰的人更容易),但这是迄今为止最好的答案,因为它突出了这样一个事实:仅忽略请求并不意味着它仍然存在没有待定。很好的答案。
2021-03-24 04:50:14
“我添加了一个指向答案的链接,让懒惰的人更容易”——根据规则,它真的应该附带一个链接和更多信息。但感谢您改进答案。
2021-04-01 04:50:14
答案并没有解释 AbortController 是什么。此外,它是实验性的,需要在不受支持的引擎中进行 polyfill,这也不是一种语法。
2021-04-11 04:50:14

如果您喜欢处理所有边缘情况的更干净的解决方案,请编辑此答案:https : //stackoverflow.com/a/57888548/1059828

我真的很喜欢这个要点中使用Promise.race的简洁方法

fetchWithTimeout.js

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

主文件

import fetch from './fetchWithTimeout'

// call as usual or with timeout as 3rd argument

fetch('http://google.com', options, 5000) // throw after max 5 seconds timeout error
.then((result) => {
    // handle result
})
.catch((e) => {
    // handle errors and timeout error
})
恕我直言,这可以在拒绝时使用 AbortController 进一步改进,请参阅stackoverflow.com/a/47250621
2021-03-16 04:50:14
如果 fetch 也成功,最好清除超时。
2021-03-20 04:50:14
如果超时fetch发生错误,这会导致“未处理的拒绝” 这可以通过处理 ( )失败并在超时尚未发生时重新抛出来解决.catchfetch
2021-03-29 04:50:14
这是一个很好的方法,但不是很有效。超时应该像Bob所说的那样清除,否则程序会一直等到超时,即使是成功的情况下
2021-04-08 04:50:14

编辑 1

正如评论中所指出的,即使在Promise被解决/拒绝之后,原始答案中的代码也会继续运行计时器。

下面的代码解决了这个问题。

function timeout(ms, promise) {
  return new Promise((resolve, reject) => {
    const timer = setTimeout(() => {
      reject(new Error('TIMEOUT'))
    }, ms)

    promise
      .then(value => {
        clearTimeout(timer)
        resolve(value)
      })
      .catch(reason => {
        clearTimeout(timer)
        reject(reason)
      })
  })
}


原答案

它没有指定的默认值;该规范根本没有讨论超时。

通常,您可以为Promise实现自己的超时包装器:

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

如描述https://github.com/github/fetch/issues/175 由评论https://github.com/mislav

尽管 'fetch' 函数因超时而被拒绝,但后台 tcp 连接并未关闭。如何优雅地退出我的节点进程?
2021-03-18 04:50:14
@radtad mislav 在该线程中为他的方法辩护:github.com/github/fetch/issues/175#issuecomment-284787564超时继续进行并不重要,因为调用.reject()已经解决的Promise没有任何作用。
2021-03-24 04:50:14
停止!这是一个错误的答案!虽然,它看起来是一个很好且有效的解决方案,但实际上连接不会被关闭,最终会占用一个 TCP 连接(甚至可能是无限的 - 取决于服务器)。想象一下这个错误的解决方案要在每段时间重试连接的系统中实现 - 这可能导致网络接口窒息(过载)并最终使您的机器挂起!@Endless 在这里发布了正确答案
2021-03-24 04:50:14
为什么这是公认的答案?即使Promise解决,这里的 setTimeout 也会继续。更好的解决方案是这样做:github.com/github/fetch/issues/175#issuecomment-216791333
2021-03-26 04:50:14
@SlavikMeltser 我不明白。您指出的答案也不会破坏 TCP 连接。
2021-04-08 04:50:14

基于 Endless 的出色回答,我创建了一个有用的实用程序功能。

const fetchTimeout = (url, ms, { signal, ...options } = {}) => {
    const controller = new AbortController();
    const promise = fetch(url, { signal: controller.signal, ...options });
    if (signal) signal.addEventListener("abort", () => controller.abort());
    const timeout = setTimeout(() => controller.abort(), ms);
    return promise.finally(() => clearTimeout(timeout));
};
  1. 如果在获取资源之前达到超时,则中止获取。
  2. 如果在达到超时之前获取资源,则清除超时。
  3. 如果中止输入信号,则中止获取并清除超时。
const controller = new AbortController();

document.querySelector("button.cancel").addEventListener("click", () => controller.abort());

fetchTimeout("example.json", 5000, { signal: controller.signal })
    .then(response => response.json())
    .then(console.log)
    .catch(error => {
        if (error.name === "AbortError") {
            // fetch aborted either due to timeout or due to user clicking the cancel button
        } else {
            // network error or json parsing error
        }
    });

希望有帮助。

这是太棒了!它涵盖了在其他答案中存在问题的所有令人讨厌的边缘情况,并且您提供了一个清晰的使用示例。
2021-03-29 04:50:14

fetch API 中还没有超时支持。但是可以通过将其包装在Promise中来实现。

例如。

  function fetchWrapper(url, options, timeout) {
    return new Promise((resolve, reject) => {
      fetch(url, options).then(resolve, reject);

      if (timeout) {
        const e = new Error("Connection timed out");
        setTimeout(reject, timeout, e);
      }
    });
  }
Firefox 自 57 年以来一直支持它。::在 Chrome 上观看::
2021-03-13 04:50:14
这很有趣,只有 IE 和 Edge 支持它!除非移动 Mozilla 网站再次活跃……
2021-03-20 04:50:14
此处超时后请求未取消,对吗?这对 OP 来说可能没问题,但有时您想取消客户端的请求。
2021-03-29 04:50:14
我更喜欢这个,重复使用不止一次。
2021-04-10 04:50:14
@trysis 好吧,是的。最近使用AbortController实现了中止获取的解决方案,但仍处于试验阶段,浏览器支持有限。讨论
2021-04-11 04:50:14