153 votes

Fetch API request timeout ?

J'ai un fetch-api POST demande :

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

Je veux savoir quel est le délai d'attente par défaut pour cela ? et comment le régler sur une valeur particulière comme 3 secondes ou des secondes indéfinies ?

168voto

Endless Points 1188

L'utilisation d'une solution de course aux promesses laissera la demande en suspens et continuera à consommer de la bande passante en arrière-plan, ce qui réduira le nombre maximal de demandes simultanées autorisées pendant qu'elle est en cours de traitement.

Utilisez plutôt le AbortController pour interrompre réellement la demande. Voici un exemple

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 peut être utilisé pour d'autres choses aussi, pas seulement pour la récupération mais aussi pour les flux lisibles/inscriptibles. Les nouvelles fonctions (en particulier celles qui sont basées sur les promesses) l'utiliseront de plus en plus. NodeJS a également implémenté AbortController dans son système de flux/fichiers. Je sais que Bluetooth Web s'y intéresse également. Maintenant, il peut également être utilisé avec l'option addEventListener et arrêter l'écoute lorsque le signal se termine.

22 votes

Cette solution semble encore meilleure que celle de la promesse-race, car elle interrompt probablement la demande au lieu de prendre simplement la réponse précédente. Corrigez-moi si je me trompe.

4 votes

La réponse n'explique pas ce qu'est AbortController. De plus, il est expérimental et doit être polyfillié dans les moteurs non supportés, ce n'est pas non plus une syntaxe.

1 votes

Elle n'explique peut-être pas ce qu'est AbortController (j'ai ajouté un lien vers la réponse pour faciliter la tâche des paresseux), mais c'est la meilleure réponse jusqu'à présent, car elle souligne le fait que le simple fait d'ignorer une demande ne signifie pas qu'elle n'est pas en attente. Excellente réponse.

161voto

abimelex Points 1180

Modifier si vous souhaitez une solution encore plus propre qui traite tous les cas limites, choisissez cette réponse : https://stackoverflow.com/a/57888548/1059828 .

J'aime vraiment l'approche propre de cette Gist en utilisant Promesse.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 votes

Cela provoque un "Unhandled rejection" si un fichier fetch une erreur se produit après temps mort. Ceci peut être résolu en traitant ( .catch ) le fetch et le relancer si le délai d'attente n'a pas encore eu lieu.

12 votes

À mon avis, cela pourrait être encore amélioré par l'utilisation d'AbortController en cas de rejet. stackoverflow.com/a/47250621 .

2 votes

Il serait préférable d'effacer le délai d'attente si la récupération est réussie.

86voto

Bruce Lee Points 942

Edit 1

Comme indiqué dans les commentaires, le code de la réponse originale continue de faire tourner le minuteur même après la résolution/le rejet de la promesse.

Le code ci-dessous corrige ce problème.

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

Réponse originale

Il n'y a pas de défaut spécifié ; la spécification ne parle pas du tout des délais d'attente.

Vous pouvez implémenter votre propre enveloppe de délai d'attente pour les promesses en général :

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

Comme décrit dans https://github.com/github/fetch/issues/175 Commentaire de https://github.com/mislav

33 votes

Pourquoi est-ce la réponse acceptée ? Le setTimeout ici va continuer même si la promesse est résolue. Une meilleure solution serait de faire ceci : github.com/github/fetch/issues/175#issuecomment-216791333

0 votes

@radtad Je pense qu'il s'agit d'une préférence personnelle quant à la façon de procéder, car la promesse ne peut pas être rejetée après avoir été résolue, donc le setTimeout n'aura aucun effet ici. Personnellement, je trouve que c'est un peu plus soigné dans la solution où le timeout est conservé.

0 votes

Si le composant est détruit pour une raison quelconque avant le délai d'attente, cela peut poser un problème.

10voto

code-jaff Points 3294

Il n'y a pas encore de support pour les délais dans l'API de récupération. Mais cela pourrait être réalisé en l'enveloppant dans une promesse.

par exemple.

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

0 votes

J'aime mieux celui-ci, moins répétitif à utiliser plus d'une fois.

2 votes

La demande n'est pas annulée après le délai d'attente ici, correct ? Cela peut convenir à l'OP, mais il arrive que l'on veuille annuler une requête côté client.

2 votes

@trysis bien, oui. J'ai récemment mis en place une solution pour l'abandon de la recherche avec AbortController mais encore expérimental avec un support limité pour les navigateurs. Discussion

9voto

Arroganz Points 72

EDIT : La requête de récupération sera toujours exécutée en arrière-plan et enregistrera très probablement une erreur dans votre console.

En effet, le Promise.race est meilleure.

Voir ce lien pour référence Promise.race()

La course signifie que toutes les promesses seront exécutées en même temps, et que la course s'arrêtera dès que l'une des promesses renverra une valeur. Par conséquent, une seule valeur sera retournée . Vous pouvez également passer une fonction à appeler si la recherche échoue.

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

Si cela suscite votre intérêt, une mise en œuvre possible serait :

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X