提取API请求超时?


106

我有一个fetch-api POST要求:

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

我想知道默认的超时时间是多少?以及如何将其设置为3秒或不确定的秒之类的特定值?

Answers:


78

编辑1

如注释中所指出的,即使在解决了诺言之后,原始答案中的代码仍继续运行计时器。

下面的代码解决了该问题。

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


原始答案

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

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

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


28
为什么这是公认的答案?即使承诺解决,此处的setTimeout也将继续进行。更好的解决方案是这样做:github.com/github/fetch/issues/175#issuecomment-216791333
radtad

3
@radtad mislav在那个线程中捍卫了他的方法:github.com/github/fetch/issues/175#issuecomment-284787564。超时一直没有关系,因为调用.reject()已解决的Promise不会执行任何操作。
Mark Amery

1
尽管超时会拒绝“获取”功能,但后台tcp连接不会关闭。如何优雅地退出节点进程?
Prog Quester

28
停!这是不正确的答案!虽然,它看起来像是一个不错的且可行的解决方案,但是实际上该连接不会关闭,最终会占用一个TCP连接(甚至可能是无限的-取决于服务器)。想象一下这个错误的解决方案要在每隔一段时间重试连接的系统中实施-这可能导致网络接口窒息(过载)并最终使您的计算机挂起!@Endless在这里发布了正确答案。
Slavik Meltser

2
@SlavikMeltser我不明白。您指出的答案也不会中断TCP连接。
Mateus Pires

147

我真的很喜欢这个干净的方法要点使用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
})

2
如果超时fetch发生错误,这将导致“未处理的拒绝” 。这可以通过处理()故障并在尚未发生超时的情况下重新抛出来解决。.catchfetch
lionello

7
恕我直言,这可以通过拒绝时使用AbortController进一步改进,请参见stackoverflow.com/a/47250621
RiZKiT

如果获取成功,则最好清除超时。
Bob9630'4

116

使用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。我知道网络蓝牙也在研究它


16
这看起来比promise-race-solution更好,因为它可能会中止请求,而不是仅仅接受较早的响应。如我错了请纠正我。
卡尔·阿德勒

3
答案并没有解释什么是AbortController。另外,它是实验性的,需要在不支持的引擎中进行填充,这也不是语法。
Estus Flask,

它可能无法解释AbortController是什么(我在答案中添加了链接,以使懒惰的查询更容易),但这是迄今为止最好的答案,因为它突出了一个事实,即仅忽略请求并不意味着它仍然存在没有待决。好答案。
Aurelio '18

2
“我在答案中添加了一个链接,以使懒惰的人更容易使用” –根据规则tbh,它确实应该带有一个链接和更多信息。但是,谢谢您改善答案。
杰伊·威克,

8
拥有这个答案总比没有答案要好,因为人们被nitpickery,tbh拒之门外
迈克尔·特里

26

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

希望能有所帮助。


2
这是太棒了!它涵盖了在其他答案中有问题的所有讨厌的极端情况,并且您提供了一个清晰的用法示例。
Atte Juvonen

8

提取API中尚无超时支持。但是可以通过将其包装在承诺中来实现。

例如

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

我喜欢这个更好,更少重复的方法,可以多次使用。
dandavis

1
超时后,请求不会被取消,对吗?这对于OP可能很好,但是有时您想取消客户端请求。
trysis'2

2
@trysis好吧,是的。最近,使用AbortController实现了中止获取的解决方案,但在有限的浏览器支持下仍处于试验阶段。讨论
code-jaff

太好笑了,只有IE&Edge支持它!除非移动Mozilla网站再次出现问题……
trysis

Firefox从57开始就一直在支持它。::在Chrome上观看::
Franklin Yu

6

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

确实,这种Promise.race方法更好。

请参阅此链接以获取参考Promise.race()

竞赛意味着所有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;
  });
}

1

您可以创建一个超时承诺包装器

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(...)  

它实际上不会取消基础连接,但可以使Promise超时。
参考


1

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

1)Firefox-90秒

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

2)Chrome-300秒

资源



-1

使用c-promise2 lib可以取消带有超时的获取,可能看起来像这样(Live jsfiddle演示):

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.