Búsqueda de sitios web

javascript - ¿Tiempo de espera de la solicitud Fetch API?


Tengo una solicitud fetch-api POST:

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

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

Respuesta #1

Hoy en día, puedes simplemente usar el recién añadido AbortSignal.timeout(5000):

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

Notas :

  • Perderás el control sobre el cierre manual de la solicitud. Tanto la carga como la descarga deberán finalizar en un tiempo total de 5 segundos.

  • Hoy en día no debería ser necesario, pero para navegadores más antiguos, si es necesario, un polyfill para ello sería:

    AbortSignal.timeout ??= function timeout(ms) {
      const ctrl = new AbortController()
      setTimeout(() => ctrl.abort(), ms)
      return ctrl.signal
    }
    
  • Respuesta anterior: Usar una solución de carrera de promesas dejará la solicitud colgada y aún consumirá ancho de banda en segundo plano y reducirá el máximo de solicitudes concurrentes permitidas mientras todavía está en proceso.

    En su lugar, utilice el AbortController para abortar realmente la solicitud. Aquí hay un ejemplo

    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 también se puede usar para otras cosas, no solo fetch, sino también para flujos legibles/escribibles. Más funciones nuevas (especialmente las basadas en promesas) lo usarán más y más. Se puede usar como un medio para detener múltiples cosas a la vez, por ejemplo, cuando sales de una SPA. NodeJS también ha implementado AbortController en sus flujos/sistema de archivos. Sé que Web Bluetooth también lo está considerando. Ahora también se puede usar con la opción addEventListener y hacer que deje de escuchar cuando la señal termina.

Respuesta #2

Actualización dado que mi respuesta original está un poco desactualizada, recomiendo usar el controlador de aborto como se implementa aquí: https://stackoverflow.com/a/57888548/1059828 o echar un vistazo a este muy buen artículo que explica el controlador de aborto con fetch: ¿Cómo cancelo una solicitud HTTP fetch()?

respuesta original desactualizada:

Me gusta mucho el enfoque limpio de este gist usando Promise.race

fetchConTiempoDeEspera.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

// throw after max 5 seconds timeout error
fetch('http://google.com', options, 5000) 
.then((result) => {
    // handle result
})
.catch((e) => {
    // handle errors and timeout error
})

Respuesta #3

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 código a continuación 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 habla de los tiempos de espera en absoluto.

Puedes implementar tu propio envoltorio de tiempo de espera para 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

Respuesta #4

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 obtenga el recurso, entonces la obtención se aborta.
  2. Si el recurso se obtiene antes de que se alcance el tiempo de espera, entonces el tiempo de espera se cancela.
  3. Si la señal de entrada se interrumpe, entonces la recuperación se interrumpe y el tiempo de espera se borra.
const controller = new AbortController();

document
  .querySelector("button.cancel")
  .addEventListener("click", () => controller.abort());

const { signal } = controller;

try {
  const response = await fetchTimeout("example.json", 5000, { signal });
  const data = await response.json();
  console.log(data);
} 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
  }
}

Respuesta #5

Una forma más limpia de hacerlo está en 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');
    }
}

Respuesta #6

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

1) Firefox - 90 segundos

Escribe about:config en el campo de URL de Firefox. Encuentra el valor correspondiente a la clave network.http.connection-timeout

2) Chrome - 300 segundos

Fuente

Respuesta #7

Aún no hay soporte de tiempo de espera en la API fetch. Pero se podría lograr envolviéndola 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);
      }
    });
  }

Respuesta #8

EDITAR: La solicitud fetch seguirá ejecutándose en segundo plano y lo más probable es que registre un error en tu consola.

De hecho, el enfoque Promise.race es mejor.

Consulta este enlace como referencia Promise.race()

Race 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 podrías pasar una función para llamar si la obtención caduca.

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

Si esto despierta tu 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;
  });
}

Artículos relacionados