获取API请求超时?

我有一个fetch-api POST请求:

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

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

252676 次浏览

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



原来的答案

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

你可以为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

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

更新因为我最初的答案有点过时,我建议使用像这里实现的中止控制器:https://stackoverflow.com/a/57888548/1059828或看看这个非常好的帖子,解释了fetch: 我如何取消一个HTTP取回()请求?的中止控制器

过时的原答案:

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

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

的确,Promise.race方法更好。

参见此链接以参考Promise.race ()

Race意味着所有promise将同时运行,并且只要其中一个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;
});
}

使用承诺竞争解决方案将使请求挂起,仍然在后台消耗带宽,并降低在处理过程中所允许的最大并发请求。

相反,使用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)…现在所有的绿色环境都有这个。您将失去手动关闭请求的控制。上传和下载都必须在5秒内完成

// a polyfill for it would be:
AbortSignal.timeout ??= function timeout(ms) {
const ctrl = new AbortController()
setTimeout(() => ctrl.close(), ms)
return ctrl.signal
}




fetch(url, { signal: AbortSignal.timeout(5000) })

AbortController也可以用于其他事情,不仅是获取,还可以用于可读/可写流。越来越多的新函数(特别是基于承诺的函数)将越来越多地使用它。NodeJS也在它的流/文件系统中实现了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(...)

它不会取消底层连接,但会允许你超时承诺 参考 < / p >

  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带超时的可取消取回可能像这样(实时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

下面是一个使用NodeJS的SSCCE,它将在1000ms后超时:

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


使用AbortControllersetTimeout;

const abortController = new AbortController();


let timer: number | null = null;


fetch('/get', {
signal: abortController.signal, // Content to abortController
})
.then(res => {
// response success
console.log(res);


if (timer) {
clearTimeout(timer); // clear timer
}
})
.catch(err => {
if (err instanceof DOMException && err.name === 'AbortError') {
// will return a DOMException
return;
}


// other errors
});


timer = setTimeout(() => {
abortController.abort();
}, 1000 * 10); // Abort request in 10s.

这是@fatcherjs/middleware-aborter中的一个片段。

通过使用fatcher,可以很容易地中止取回请求。

import { aborter } from '@fatcherjs/middleware-aborter';
import { fatcher, isAbortError } from 'fatcher';


fatcher({
url: '/bar/foo',
middlewares: [
aborter({
timeout: 10 * 1000, // 10s
onAbort: () => {
console.log('Request is Aborted.');
},
}),
],
})
.then(res => {
// Request success in 10s
console.log(res);
})
.catch(err => {
if (isAbortError(err)) {
//Run error when request aborted.
console.error(err);
}


// Other errors.
});