¿Obtener tiempo de espera de solicitud de API?

100

Tengo una fetch-api POSTsolicitud:

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

Quiero saber cuál es el tiempo de espera predeterminado para esto. y ¿cómo podemos establecerlo en un valor particular como 3 segundos o segundos indefinidos?

Akshay Lokur
fuente

Respuestas:

78

Editar 1

Como se señaló en los comentarios, el código en la respuesta original sigue ejecutando el temporizador incluso después de que la promesa se resuelve / rechaza.

El siguiente código soluciona ese 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)
      })
  })
}


Respuesta original

No tiene un valor predeterminado especificado; la especificación no trata los tiempos de espera en absoluto.

Puede implementar su propio contenedor de tiempo de espera para las promesas en general:

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

Como se describe en https://github.com/github/fetch/issues/175 Comentario de https://github.com/mislav

agitar
fuente
27
¿Por qué es esta la respuesta aceptada? El setTimeout aquí continuará incluso si la promesa se resuelve. Una mejor solución sería hacer esto: github.com/github/fetch/issues/175#issuecomment-216791333
radtad
3
@radtad mislav defiende su enfoque más abajo en ese hilo: github.com/github/fetch/issues/175#issuecomment-284787564 . No importa que el tiempo de espera continúe, porque invocar .reject()una promesa que ya se ha resuelto no hace nada.
Mark Amery
1
aunque la función 'buscar' es rechazada por el tiempo de espera, la conexión tcp en segundo plano no se cierra. ¿Cómo puedo salir de mi proceso de nodo correctamente?
Prog Quester
26
¡DETENER! ¡Esta es una respuesta incorrecta! Aunque parece una solución buena y funcional, pero en realidad la conexión no se cerrará, lo que eventualmente ocupará una conexión TCP (podría ser incluso infinita, depende del servidor). Imagine que esta solución INCORRECTA se implementará en un sistema que reintenta una conexión cada período de tiempo. ¡Esto podría llevar a la asfixia (sobrecarga) de la interfaz de red y hacer que su máquina se cuelgue eventualmente! @Endless publicó la respuesta correcta aquí .
Slavik Meltser
1
@SlavikMeltser No lo entiendo. La respuesta que señaló tampoco rompe la conexión TCP.
Mateus Pires
143

Realmente me gusta el enfoque limpio de esta esencia 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
})
Karl Adler
fuente
2
Esto provoca un "rechazo no controlado" si fetchocurre un error después del tiempo de espera. Esto se puede resolver manejando ( .catch) la fetchfalla y volviendo a lanzar si el tiempo de espera aún no ha ocurrido.
lionello
5
En mi humilde opinión, esto podría mejorarse aún más con AbortController al rechazar, consulte stackoverflow.com/a/47250621 .
RiZKiT
Sería mejor borrar el tiempo de espera si la recuperación también se realiza correctamente.
Bob9630
105

Con AbortController , podrá hacer esto:

const controller = new AbortController();
const signal = controller.signal;

const fetchPromise = fetch(url, {signal});

// 5 second timeout:
const timeoutId = setTimeout(() => controller.abort(), 5000);


fetchPromise.then(response => {
  // completed request before timeout fired

  // If you only wanted to timeout the request, not the response, add:
  // clearTimeout(timeoutId);
})
Interminable
fuente
14
Esto se ve incluso mejor que la solución de carrera de promesa porque probablemente anula la solicitud en lugar de simplemente tomar la respuesta anterior. Corrígeme si me equivoco.
Karl Adler
3
La respuesta no explica qué es AbortController. Además, es experimental y debe ser polyrellenado en motores no compatibles, además no es una sintaxis.
Estus Flask
Puede que no explique qué es AbortController (agregué un enlace a la respuesta para que sea más fácil para los perezosos), pero esta es la mejor respuesta hasta ahora, ya que resalta el hecho de que simplemente ignorar una solicitud no significa que todavía esté no pendiente. Gran respuesta.
Aurelio
2
"Agregué un enlace a la respuesta para que sea más fácil para los perezosos" - realmente debería venir con un enlace y más información según las reglas tbh. Pero gracias por mejorar la respuesta.
Jay Wick
6
Es mejor tener esta respuesta que ninguna respuesta porque la gente se siente desanimada por los quisquillosos, tbh
Michael Terry
21

Basándome en la excelente respuesta de Endless , creé una función de utilidad ú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. Si se alcanza el tiempo de espera antes de que se recupere el recurso, la recuperación se aborta.
  2. Si el recurso se recupera antes de que se alcance el tiempo de espera, se borra el tiempo de espera.
  3. Si se cancela la señal de entrada, se cancela la búsqueda y se borra el tiempo de espera.
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 ayude.

Aadit M Shah
fuente
9

todavía no hay soporte de tiempo de espera en la API de recuperación. Pero podría lograrse envolviéndolo en una promesa.

por ej.

  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);
      }
    });
  }
código-jaff
fuente
Me gusta más este, menos repetitivo para usar más de una vez.
dandavis
1
La solicitud no se cancela después del tiempo de espera aquí, ¿correcto? Esto puede estar bien para el OP, pero a veces desea cancelar una solicitud del lado del cliente.
Trysis
2
@trysis bueno, sí. Recientemente se implementó una solución para abortar la búsqueda con AbortController , pero aún es experimental con compatibilidad limitada con el navegador. Discusión
code-jaff
¡Qué curioso, IE y Edge son los únicos que lo admiten! A menos que el sitio móvil de Mozilla vuelva a funcionar ...
Trysis
Firefox lo ha estado apoyando desde 57. :: viendo en Chrome ::
Franklin Yu
7

EDITAR : La solicitud de recuperación seguirá ejecutándose en segundo plano y lo más probable es que registre un error en su consola.

De hecho, el Promise.raceenfoque es mejor.

Vea este enlace para referencia Promise.race ()

Carrera significa que todas las promesas se ejecutarán al mismo tiempo y la carrera se detendrá tan pronto como una de las promesas devuelva un valor. Por lo tanto, solo se devolverá un valor . También puede pasar una función para llamar si se agota el tiempo de recuperación.

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

Si esto despierta su interés, una posible implementación sería:

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;
  });
}
Arroganz
fuente
2

Puede crear un contenedor timeoutPromise

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

Luego puedes envolver cualquier promesa

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

En realidad, no cancelará una conexión subyacente, pero le permitirá agotar una promesa.
Referencia

Pulkit Aggarwal
fuente
2

Si no ha configurado el tiempo de espera en su código, será el tiempo de espera de solicitud predeterminado de su navegador.

1) Firefox - 90 segundos

Escriba about:configen el campo URL de Firefox. Encuentra el valor correspondiente a la clavenetwork.http.connection-timeout

2) Chrome: 300 segundos

Fuente

Harikrishnan
fuente
0
  fetchTimeout (url,options,timeout=3000) {
    return new Promise( (resolve, reject) => {
      fetch(url, options)
      .then(resolve,reject)
      setTimeout(reject,timeout);
    })
  }
Mojimi
fuente
Esto es más o menos lo mismo que stackoverflow.com/a/46946588/1008999 pero tiene un tiempo de espera predeterminado
sin fin
0

Usando c-promise2 lib, la recuperación cancelable con tiempo de espera podría verse así ( demostración de jsfiddle en vivo ):

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 paquete npm cp-fetch

Dmitriy Mozgovoy
fuente