Fetch API 请求超时?

Fetch API request timeout?

我有一个 fetch-api POST 请求:

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

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

编辑 1

正如评论中指出的那样,即使在承诺 resolved/rejected 之后,原始答案中的代码仍会保持 运行 计时器。

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

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 根本不讨论超时。

您可以为一般的承诺实现自己的超时包装器:

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

发表评论

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

Update 因为我原来的答案有点过时我建议使用像这里实现的中止控制器: or take a look at this really good post explaining abort controller with fetch:

过时的原始答案:

我真的很喜欢这种干净的方法 gist using 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
})

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

确实 Promise.race 方法更好。

参考这个linkPromise.race()

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

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

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

您可以使用新添加的替代方案 AbortSignal.timeout(5000)... 但目前在大多数浏览器中并没有得到很好的实现。


AbortController 也可以用于其他用途,不仅可以获取数据,还可以用于 readable/writable 流。越来越多的新功能(特别是基于承诺的功能)将越来越多地使用它。 NodeJS 也在其 streams/filesystem 中实现了 AbortController。我知道网络蓝牙也在研究它。现在它也可以与 addEventListener 选项一起使用,并让它在信号结束时停止监听

您可以创建一个 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

  fetchTimeout (url,options,timeout=3000) {
    return new Promise( (resolve, reject) => {
      fetch(url, options)
      .then(resolve,reject)
      setTimeout(reject,timeout);
    })
  }

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

希望对您有所帮助。

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

1) Firefox - 90 秒

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

对应的值

2) Chrome - 300 秒

Source

使用c-promise2 lib the cancellable fetch with timeout might look like this one (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

这是一个使用 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);
});

实际上在 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');
    }
}