API 요청 시간 초과를 가져 오시겠습니까?


107

나는이 fetch-api POST요청을 :

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

이에 대한 기본 시간 제한이 무엇인지 알고 싶습니다. 3 초 또는 무한 초와 같은 특정 값으로 어떻게 설정할 수 있습니까?

답변:


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()이미 해결 된 약속을 호출 하면 아무 일도 일어나지 않기 때문에 시간 초과가 계속되는 것은 중요 하지 않습니다.
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
IMHO 거부 할 때 AbortController를 사용하면 더 개선 될 수 있습니다 . stackoverflow.com/a/47250621을 참조하십시오 .
RiZKiT

가져 오기도 성공하면 시간 제한을 지우는 것이 좋습니다.
Bob9630

116

약속 경주 솔루션을 사용하면 요청이 중단되고 백그라운드에서 여전히 대역폭을 소비하고 처리중인 동안 허용되는 최대 동시 요청이 낮아집니다.

대신 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
이는 약속-경주-솔루션보다 훨씬 더 좋아 보입니다. 왜냐하면 아마도 이전 응답을 취하는 대신 요청을 중단하기 때문입니다. 틀 렸으면 말해줘.
Karl Adler

3
대답은 AbortController가 무엇인지 설명하지 않습니다. 또한 실험적이며 지원되지 않는 엔진에서 폴리 필해야하며 구문도 아닙니다.
Estus Flask

AbortController가 무엇인지 설명하지 않을 수도 있지만 (게으른 사람들을 더 쉽게 만들기 위해 답변에 대한 링크를 추가했습니다), 이것은 요청을 무시하는 것이 여전히 보류 중이 아닙니다. 좋은 대답입니다.
Aurelio

2
"나는 게으른 사람들이 더 쉽게 할 수 있도록 대답에 대한 링크를 추가했습니다."-규칙 tbh에 따라 실제로 링크와 더 많은 정보가 함께 제공되어야합니다. 그러나 답변을 개선해 주셔서 감사합니다.
Jay Wick

8
사람들이 TBH, nitpickery에 의해 넣어 오프이기 때문에 더 나은없이 대답보다는이 대답을합니다
마이클 테리

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

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

나는 이것을 두 번 이상 사용하는 것이 더 좋고 덜 반복적입니다.
dandavis

1
여기에서 시간 초과 후에도 요청이 취소되지 않습니다. 맞습니까? 이것은 OP에는 괜찮을 수 있지만 때로는 클라이언트 측 요청을 취소하고 싶을 때가 있습니다.
trysis

2
@trysis 잘, 네. 최근 AbortController를 사용 하여 가져 오기 중단에 대한 솔루션을 구현 했지만 제한된 브라우저 지원으로 아직 실험 중입니다. 토론
code-jaff 2018

재미 있네요. IE와 Edge가이를 지원하는 유일한 제품입니다! 모바일 Mozilla 사이트가 다시 작동하지 않는 한 ...
trysis

Firefox는 57 년부터 지원해 왔습니다. :: watching at Chrome ::
Franklin Yu

6

편집 : 가져 오기 요청은 여전히 ​​백그라운드에서 실행 중이며 콘솔에 오류가 기록 될 가능성이 높습니다.

실제로 Promise.race접근 방식이 더 좋습니다.

참조 Promise.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;
  });
}

1

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

실제로 기본 연결을 취소하지는 않지만 약속 시간을 초과 할 수 있습니다.
참고


1

코드에서 시간 제한을 구성하지 않은 경우 브라우저의 기본 요청 시간 제한이됩니다.

1) Firefox-90 초

about:configFirefox URL 필드에 입력하십시오 . 키에 해당하는 값 찾기network.http.connection-timeout

2) 크롬-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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.