获取API请求超时?

Aks*_*kur 65 javascript ajax fetch-api

我有一个fetch-api POST要求:

   fetch(url, {
      method: 'POST',
      body: formData,
      credentials: 'include'
    })
Run Code Online (Sandbox Code Playgroud)

我想知道这是什么默认超时?我们如何将其设置为特定值,如3秒或无限秒?

Kar*_*ler 93

我真的很喜欢使用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)
        )
    ]);
}
Run Code Online (Sandbox Code Playgroud)

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
})
Run Code Online (Sandbox Code Playgroud)

  • 恕我直言,这可以在拒绝时使用 AbortController 进一步改进,请参阅 /sf/answers/3307543501/。 (12认同)
  • 如果在*超时后发生`fetch`错误,这会导致“未处理的拒绝”。这可以通过处理(`.catch`)`fetch`失败并在超时尚未发生时重新抛出来解决。 (2认同)
  • 如果 fetch 也成功的话,最好清除超时。 (2认同)
  • @jfunk那不是真的。AbortController 在 Nodejs v15 及更高版本中可用。 (2认同)

sha*_*eel 68

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

您可以为promises实现自己的超时包装器:

// 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
})
Run Code Online (Sandbox Code Playgroud)

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

  • **停止!** 这是一个错误的答案!虽然,它看起来是一个很好且有效的解决方案,但实际上连接不会被关闭,这最终会占用一个 TCP 连接(甚至可能是无限的 - 取决于服务器)。想象一下,这个错误的解决方案是在一个每隔一段时间重试连接的系统中实现的 - 这可能会导致网络接口窒息(过载)并最终使您的计算机挂起!@Endless 在[此处](/sf/answers/3490053381/)发布了正确的答案。 (37认同)
  • 为什么这是公认的答案?即使承诺解决,此处的setTimeout也将继续进行。更好的解决方案是这样做:https://github.com/github/fetch/issues/175#issuecomment-216791333 (4认同)
  • @radtad Mislav 在该线程下方捍卫了他的方法:https://github.com/github/fetch/issues/175#issuecomment-284787564。超时持续下去并不重要,因为对已经解决的 Promise 调用 `.reject()` 不会执行任何操作。 (3认同)
  • @SlavikMeltser 我不明白。您指出的答案也不会中断 TCP 连接。 (3认同)

End*_*ess 54

使用中止语法,您将能够:

const controller = new AbortController();
const signal = controller.signal;

const fetchPromise = fetch(url, {signal});

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


fetchPromise.then(response => {
  // completed request before timeout fired

  // If you only wanted to timeout the request, not the response, add:
  // clearTimeout(timeoutId);
})
Run Code Online (Sandbox Code Playgroud)

请参阅MDN上的AbortController页面.

  • 这看起来甚至比promise-race-solution更好,因为它可能会中止请求而不是仅仅采用早期的响应.如我错了请纠正我. (8认同)
  • 它可能无法解释 AbortController 是什么(我添加了一个答案的链接,以便懒惰的人更容易理解),但这是迄今为止最好的答案,因为它强调了这样一个事实:仅仅忽略请求并不意味着它仍然存在没有待定。很好的答案。 (4认同)
  • 拥有这个答案总比没有答案要好,因为人们被nitpickery(TBH)拖走了 (4认同)
  • @EstusFlask 目前[广泛支持](https://caniuse.com/?search=AbortController) (4认同)
  • 答案并没有解释 AbortController 是什么。此外,它是实验性的,需要在不受支持的引擎中进行 polyfill,这也不是一种语法。 (3认同)
  • “我添加了一个指向答案的链接,以使懒惰的人更容易” - 根据规则,它确实应该带有链接和更多信息。但感谢您改进答案。 (2认同)
  • 为了捕捉超时,我发现以下代码非常有用,由原始 Google 工程师提供,我认为这对于初学者可能有用:https://developers.google.com/web/updates/2017/09/abortable-fetch - fetch(url, { signal }).then(response => { return response.text(); }).then(text => { console.log(text); }).catch(err => { if ( err.name === 'AbortError') { console.log('获取中止'); } else { console.error('呃哦,错误!', err); } }); (2认同)

Aad*_*hah 38

基于 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));
};
Run Code Online (Sandbox Code Playgroud)
  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
        }
    });
Run Code Online (Sandbox Code Playgroud)

希望有帮助。

  • 这是太棒了!它涵盖了其他答案中存在问题的所有令人讨厌的边缘情况,_并且_您提供了一个清晰的使用示例。 (2认同)

aGu*_*egu 31

更简洁的方法实际上是在 MDN 中:https ://developer.mozilla.org/en-US/docs/Web/API/AbortSignal#aborting_a_fetch_operation_with_a_timeout

try {
    await fetch(url, { signal: AbortSignal.timeout(5000) });
} catch (e) {
    if (e.name === "TimeoutError") {
        console.log('5000 ms timeout');
    }
}

Run Code Online (Sandbox Code Playgroud)


Har*_*nan 17

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

1) 火狐 - 90 秒

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

2) Chrome - 300 秒

来源


小智 9

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

确实Promise.race方法更好。

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

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

fetchWithTimeout(url, {
  method: 'POST',
  body: formData,
  credentials: 'include',
}, 5000, () => { /* do stuff here */ });
Run Code Online (Sandbox Code Playgroud)

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

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;
  });
}
Run Code Online (Sandbox Code Playgroud)


cod*_*aff 6

提取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);
      }
    });
  }
Run Code Online (Sandbox Code Playgroud)

  • 这里超时后请求并没有被取消,对吗?这对于 OP 来说可能没问题,但有时您想取消客户端请求。 (5认同)
  • @trysis好吧,是的。最近,通过[AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController)实现了中止获取的解决方案,但仍在有限的浏览器支持下进行试验。[讨论](https://github.com/whatwg/fetch/issues/447) (2认同)

8bi*_*kie 5

这是使用 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);
});
Run Code Online (Sandbox Code Playgroud)