Buscar tempo limite de solicitação de API?


106

Eu tenho um fetch-api POSTpedido:

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

Quero saber qual é o tempo limite padrão para isso? e como podemos defini-lo para um valor específico como 3 segundos ou segundos indefinidos?

Respostas:


78

Editar 1

Conforme apontado nos comentários, o código na resposta original continua executando o cronômetro mesmo depois que a promessa é resolvida / rejeitada.

O código abaixo corrige esse problema.

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


Resposta original

Não tem um padrão especificado; a especificação não discute os tempos limite.

Você pode implementar seu próprio wrapper de tempo limite para promessas em geral:

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

Conforme descrito em https://github.com/github/fetch/issues/175 Comentário por https://github.com/mislav


28
Por que essa é a resposta aceita? O setTimeout aqui continuará mesmo se a promessa for resolvida. Uma solução melhor seria fazer isso: github.com/github/fetch/issues/175#issuecomment-216791333
radtad

3
@radtad mislav defende sua abordagem mais abaixo nesse tópico: github.com/github/fetch/issues/175#issuecomment-284787564 . Não importa que o tempo limite continue, porque invocar .reject()uma promessa que já foi resolvida não adianta nada.
Mark Amery

1
embora a função 'fetch' seja rejeitada por tempo limite, a conexão tcp em segundo plano não é fechada. Como posso sair do meu processo de nó normalmente?
Prog Quester

28
PARE! Esta é uma resposta incorreta! Embora, pareça uma solução boa e funcional, mas na verdade a conexão não será fechada, o que eventualmente ocupará uma conexão TCP (pode ser até infinita - depende do servidor). Imagine esta solução ERRADA a ser implementada em um sistema que tenta novamente uma conexão a cada período de tempo - Isso pode levar à sufocação da interface de rede (sobrecarga) e fazer sua máquina travar eventualmente! @Endless postou a resposta correta aqui .
Slavik Meltser

2
@SlavikMeltser Não entendi. A resposta que você apontou também não interrompe a conexão TCP.
Mateus Pires

147

Eu realmente gosto da abordagem limpa desta essência usando 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
Isso causa uma "rejeição não tratada" se fetchocorrer um erro após o tempo limite. Isso pode ser resolvido manipulando ( .catch) a fetchfalha e relançando se o tempo limite ainda não aconteceu.
lionello de

7
IMHO, isso pode ser melhorado com AbortController ao rejeitar, consulte stackoverflow.com/a/47250621 .
RiZKiT

Seria melhor limpar o tempo limite se a busca também for bem-sucedida.
Bob9630

116

Usar uma solução de corrida de promessa deixará a solicitação suspensa e ainda consumirá largura de banda em segundo plano e reduzirá o máximo de solicitações simultâneas permitidas enquanto ainda está em processo.

Em vez disso, use o AbortController para realmente abortar a solicitação. Aqui está um exemplo

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 também pode ser usado para outras coisas, não apenas para buscar, mas também para fluxos legíveis / graváveis. Funções mais novas (especialmente aquelas baseadas em promessa) usarão isso cada vez mais. NodeJS também implementou AbortController em seus streams / sistema de arquivos também. Eu sei que o bluetooth da web também está investigando isso


16
Isso parece ainda melhor do que a solução de corrida-promessa porque provavelmente aborta a solicitação em vez de apenas obter a resposta anterior. Corrija-me se eu estiver errado.
Karl Adler

3
A resposta não explica o que é AbortController. Além disso, é experimental e precisa ser polyfilled em motores não suportados, também não é uma sintaxe.
Estus Flask

Pode não explicar o que é AbortController (adicionei um link para a resposta para tornar mais fácil para os preguiçosos), mas esta é a melhor resposta até agora, pois destaca o fato de que simplesmente ignorar uma solicitação não significa que ela ainda não pendente. Ótima resposta.
Aurelio

2
“Eu adicionei um link para a resposta para tornar mais fácil para os preguiçosos” - deveria realmente vir com um link e mais informações conforme as regras tbh. Mas obrigado por melhorar a resposta.
Jay Wick de

8
Melhor ter essa resposta do que nenhuma resposta, porque as pessoas são desencorajadas por detalhes, tbh
Michael Terry

26

Com base na excelente resposta de Endless , criei uma função de utilidade útil.

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. Se o tempo limite for atingido antes que o recurso seja obtido, a busca será abortada.
  2. Se o recurso for buscado antes que o tempo limite seja atingido, o tempo limite será limpo.
  3. Se o sinal de entrada for abortado, a busca será abortada e o tempo limite apagado.
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
        }
    });

Espero que ajude.


2
Isto é fantástico! Ele cobre todos os casos extremos desagradáveis ​​que foram problemáticos em outras respostas e você fornece um exemplo de uso claro.
Atte Juvonen

8

ainda não há suporte para tempo limite na API de busca. Mas isso poderia ser alcançado envolvendo-o em uma promessa.

por exemplo.

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

eu gosto mais deste, menos repetitivo para usar mais de uma vez.
dandavis

1
A solicitação não é cancelada após o timeout aqui, correto? Isso pode ser bom para o OP, mas às vezes você deseja cancelar uma solicitação do lado do cliente.
trysis 01 de

2
@trysis bem, sim. Implementou recentemente uma solução para abort fetch com AbortController , mas ainda experimental com suporte de navegador limitado. Discussão
code-jaff

Isso é engraçado, o IE e o Edge são os únicos que o suportam! A menos que o site móvel da Mozilla esteja com problemas novamente ...
trysis

O Firefox oferece suporte desde 57. :: assistindo no Chrome ::
Franklin Yu

6

EDITAR : A solicitação de busca ainda estará em execução em segundo plano e provavelmente registrará um erro em seu console.

Na verdade, a Promise.raceabordagem é melhor.

Veja este link para referência Promise.race ()

Corrida significa que todas as promessas serão realizadas ao mesmo tempo e a corrida será interrompida assim que uma das promessas retornar um valor. Portanto, apenas um valor será retornado . Você também pode passar uma função para chamar se a busca expirar.

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

Se isso despertar seu interesse, uma possível implementação seria:

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

Você pode criar um wrapper timeoutPromise

function timeoutPromise(timeout, err, promise) {
  return new Promise(function(resolve,reject) {
    promise.then(resolve,reject);
    setTimeout(reject.bind(null,err), timeout);
  });
}

Você pode então quebrar qualquer promessa

timeoutPromise(100, new Error('Timed Out!'), fetch(...))
  .then(...)
  .catch(...)  

Na verdade, isso não cancelará uma conexão subjacente, mas permitirá que você expire uma promessa.
Referência


1

Se você não configurou o tempo limite em seu código, será o tempo limite de solicitação padrão do seu navegador.

1) Firefox - 90 segundos

Digite about:configno campo URL do Firefox. Encontre o valor correspondente à chavenetwork.http.connection-timeout

2) Chrome - 300 segundos

Fonte


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

É praticamente o mesmo que stackoverflow.com/a/46946588/1008999, mas você tem um tempo limite padrão
Endless

-1

Usando c-promessa2 lib, a busca cancelável com tempo limite pode ser semelhante a esta ( demonstração ao vivo do 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

Este código como um pacote npm cp-fetch

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.