获取 API 请求超时?

Posted

技术标签:

【中文标题】获取 API 请求超时?【英文标题】:Fetch API request timeout? 【发布时间】:2018-04-07 08:49:06 【问题描述】:

我有一个fetch-api POST 请求:

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

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

【问题讨论】:

【参考方案1】:

使用 Promise Race 解决方案会使请求挂起,但仍会在后台消耗带宽,并降低仍在处理中的最大允许并发请求。

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

【讨论】:

这看起来比 promise-race-solution 更好,因为它可能会中止请求,而不是仅仅接受较早的响应。如果我错了,请纠正我。 答案没有解释 AbortController 是什么。此外,它是实验性的,需要在不受支持的引擎中进行 polyfill,它也不是语法。 它可能无法解释 AbortController 是什么(我添加了答案的链接以使懒惰的人更容易),但这是迄今为止最好的答案,因为它突出了一个事实,即仅仅忽略请求并不意味着它仍然没有待处理。很好的答案。 “我添加了一个指向答案的链接,让懒惰的人更容易”——根据规则,它确实应该带有一个链接和更多信息。但感谢您改进答案。 有这个答案总比没有答案好,因为人们被吹毛求疵,tbh【参考方案2】:

编辑如果您喜欢更简洁的解决方案来处理所有边缘情况,请选择以下答案:https://***.com/a/57888548/1059828。

我真的很喜欢 gist 使用 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)
        )
    ]);

main.js

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
)

【讨论】:

如果fetch 错误发生在 超时之后,则会导致“未处理的拒绝”。这可以通过处理 (.catch) fetch 失败并在超时尚未发生时重新抛出来解决。 恕我直言,这可以在拒绝时通过 AbortController 进一步改进,请参阅***.com/a/47250621。 如果fetch也成功了最好清除超时。 这是一个很好的方法,但不是很有效。应该按照Bob说的清除超时,否则程序会一直等到超时,即使成功了【参考方案3】:

编辑 1

正如 cmets 中所指出的,即使在 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)
      )
  )



原答案

它没有指定的默认值; the specification 根本不讨论超时。

您可以为一般的 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

【讨论】:

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

在 Endless 的优秀 answer 的基础上,我创建了一个有用的实用函数。

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));
;
    如果在提取资源之前达到超时,则提取将中止。 如果在达到超时之前获取资源,则超时被清除。 如果输入信号中止,则提取中止并清除超时。
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
        
    );

希望对您有所帮助。

【讨论】:

这太棒了!它涵盖了其他答案中存在问题的所有令人讨厌的边缘情况,并且您提供了一个清晰的使用示例。【参考方案5】:

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

【讨论】:

我更喜欢这个,重复使用次数更少。 这里超时后请求没有被取消,对吧?这对 OP 来说可能没问题,但有时您想在客户端取消请求。 @trysis 好吧,是的。最近使用AbortController 实现了一个中止提取的解决方案,但仍然在有限的浏览器支持下进行实验。 Discussion 这很有趣,IE & Edge 是唯一支持它的!除非移动 Mozilla 网站再次出现问题...... Firefox 从 57 年就开始支持它。 ::watching at Chrome::【参考方案6】:

编辑:获取请求仍将在后台运行,并且很可能会在您的控制台中记录错误。

确实Promise.race 方法更好。

参考此链接Promise.race()

Race 意味着所有 Promises 将同时运行,并且一旦其中一个 Promise 返回值,比赛就会停止。 因此,只会返回一个值。 如果提取超时,您还可以传递一个函数来调用。

fetchWithTimeout(url, 
  method: 'POST',
  body: formData,
  credentials: 'include',
, 5000, () =>  /* do stuff here */ );

如果这激起了您的兴趣,一个可能的实现是:

function fetchWithTimeout(url, options, delay, onTimeout) 
  const timer = new Promise((resolve) => 
    setTimeout(resolve, delay, 
      timeout: true,
    );
  );
  return Promise.race([
    fetch(url, options),
    timer
  ]).then(response => 
    if (response.timeout) 
      onTimeout();
    
    return response;
  );

【讨论】:

【参考方案7】:

如果您的代码中没有配置超时,它将是您浏览器的默认请求超时。

1) Firefox - 90 秒

在 Firefox URL 字段中输入 about:config。找到键network.http.connection-timeout对应的值

2) Chrome - 300 秒

Source

【讨论】:

【参考方案8】:
  fetchTimeout (url,options,timeout=3000) 
    return new Promise( (resolve, reject) => 
      fetch(url, options)
      .then(resolve,reject)
      setTimeout(reject,timeout);
    )
  

【讨论】:

这和***.com/a/46946588/1008999差不多,但是你有一个默认的超时时间【参考方案9】:

你可以创建一个 timeoutPromise 包装器

function timeoutPromise(timeout, err, promise) 
  return new Promise(function(resolve,reject) 
    promise.then(resolve,reject);
    setTimeout(reject.bind(null,err), timeout);
  );

然后你可以包装任何承诺

timeoutPromise(100, new Error('Timed Out!'), fetch(...))
  .then(...)
  .catch(...)  

它实际上不会取消底层连接,但可以让你超时。Reference

【讨论】:

【参考方案10】:

这是一个使用 NodeJS 的 SSCCE,它将在 1000 毫秒后超时:

import fetch from 'node-fetch';

const controller = new AbortController();
const timeout = setTimeout(() => 
    controller.abort();
, 1000); // will time out after 1000ms

fetch('https://www.yourexample.com', 
    signal: controller.signal,
    method: 'POST',
    body: formData,
    credentials: 'include'

)
.then(response => response.json())
.then(json => console.log(json))
.catch(err => 
    if(err.name === 'AbortError') 
        console.log('Timed out');
    
)
.finally( () => 
    clearTimeout(timeout);
);

【讨论】:

【参考方案11】:

使用c-promise2 lib 可取消的超时提取可能看起来像这样(Live jsfiddle demo):

import CPromise from "c-promise2"; // npm package

function fetchWithTimeout(url, timeout, ...fetchOptions= ) 
    return new CPromise((resolve, reject, signal) => 
        fetch(url, ...fetchOptions, signal).then(resolve, reject)
    , timeout)

        
const chain = fetchWithTimeout("https://run.mocky.io/v3/753aa609-65ae-4109-8f83-9cfe365290f0?mocky-delay=10s", timeout: 5000)
    .then(request=> console.log('done'));
    
// chain.cancel(); - to abort the request before the timeout

此代码为 npm 包cp-fetch

【讨论】:

以上是关于获取 API 请求超时?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Java 中为 BigQuery API 请求设置超时

如何测试客户端代码是不是正确超时 API 请求到外部服务器

ping对方的IP,为啥总是显示“请求超时”?

Solana JSON RPC 请求超时

Spring Boot WebClient OAuth - 同时命中多个请求时超时

实战:第二十三章:接入第三方api访问连接超时,httpclient请求超时问题