簡體   English   中英

獲取 API 請求超時?

[英]Fetch API request timeout?

我有一個fetch-api POST請求:

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

我想知道這個的默認超時是多少? 我們如何將它設置為特定值,如 3 秒或無限秒?

使用 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 也可以用於其他事情,不僅可以用於獲取,還可以用於可讀/可寫流。 更多更新的功能(特別是基於承諾的功能)將越來越多地使用它。 NodeJS 也在其流/文件系統中實現了 AbortController。 我知道網絡藍牙也在研究它。 現在它也可以與 addEventListener 選項一起使用,並在信號結束時停止監聽

更新,因為我的原始答案有點過時了,我建議使用像這里實現的中止控制器: https ://stackoverflow.com/a/57888548/1059828 或看看這篇非常好的帖子,解釋使用 fetch 中止控制器: 如何取消HTTP fetch() 請求?

過時的原始答案:

我真的很喜歡這個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
})

編輯 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

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

希望有幫助。

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

編輯:獲取請求仍將在后台運行,並且很可能會在您的控制台中記錄錯誤。

確實, 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;
  });
}

如果您沒有在代碼中配置超時,它將是您瀏覽器的默認請求超時。

1) 火狐 - 90 秒

在 Firefox URL 字段中輸入about:config 找到key network.http.connection-timeout對應的值

2) 鉻 - 300 秒

資源

一種更簡潔的方法實際上是在 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');
    }
}

這是一個使用 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);
});

您可以創建一個 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(...)  

它實際上不會取消底層連接,但允許您超時承諾。
參考

  fetchTimeout (url,options,timeout=3000) {
    return new Promise( (resolve, reject) => {
      fetch(url, options)
      .then(resolve,reject)
      setTimeout(reject,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.
    });

正確的錯誤處理技巧


正常做法:

大多數時候要添加超時支持,建議引入這樣的Promise實用函數:

function fetchWithTimeout(resource, { signal, timeout, ...options } = {}) {
  const controller = new AbortController();
  if (signal != null) signal.addEventListener("abort", controller.abort);
  const id = timeout != null ? setTimeout(controller.abort) : undefined;
  return fetch(resource, {
    ...options,
    signal: controller.signal
  }).finally(() => if (id != null) clearTimeout(id)));
}

setTimeout回調函數中調用controller.abort或拒絕承諾會扭曲堆棧跟蹤。

這是次優的,因為如果需要事后日志分析,則必須在調用fetch方法的函數中添加帶有日志消息的樣板錯誤處理程序。


良好的專業知識:

要保留錯誤及其堆棧跟蹤,可以應用以下技術:

 // Utility methods const abortId = Symbol("request abort"); function sleep(ms = 0) { return new Promise((resolve) => { setTimeout(() => resolve(), ms); }); } async function fetchWithTimeout(resource, { signal, timeout, ...options } = {}) { const controller = new AbortController(); if (signal.= null) signal,addEventListener("abort". controller;abort), const request = fetch(resource. {..,options: signal. controller;signal }). if (timeout;= null) { const aborter = sleep(timeout).then(() => abortId), const race = await Promise;race([aborter. request]); if (typeof race === "symbol") { controller;abort(). } } return request. } // End Utility methods // Test function async function test() { try { const url = new URL(window;location,href): await fetchWithTimeout(url; { timeout. 5 }); console.info(`Request sent`), } catch (error) { console;error("Error in test"; error); } } test();

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM