API-Anforderungszeitlimit abrufen?


99

Ich habe eine fetch-api POSTAnfrage:

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

Ich möchte wissen, wie hoch das Standardzeitlimit dafür ist. und wie können wir es auf einen bestimmten Wert wie 3 Sekunden oder unbestimmte Sekunden einstellen?

Antworten:


78

Bearbeiten 1

Wie in den Kommentaren erwähnt, läuft der Code in der ursprünglichen Antwort den Timer weiter, auch nachdem das Versprechen gelöst / abgelehnt wurde.

Der folgende Code behebt dieses Problem.

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


Ursprüngliche Antwort

Es gibt keine festgelegte Standardeinstellung. In der Spezifikation werden Zeitüberschreitungen überhaupt nicht behandelt.

Sie können Ihren eigenen Timeout-Wrapper für Versprechen im Allgemeinen implementieren:

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

Wie unter https://github.com/github/fetch/issues/175 beschrieben. Kommentar von https://github.com/mislav


27
Warum ist dies die akzeptierte Antwort? Das setTimeout hier wird auch dann fortgesetzt, wenn das Versprechen aufgelöst wird. Eine bessere Lösung wäre dies: github.com/github/fetch/issues/175#issuecomment-216791333
radtad

3
@radtad mislav verteidigt seinen Ansatz weiter unten in diesem Thread: github.com/github/fetch/issues/175#issuecomment-284787564 . Es spielt keine Rolle, dass die Zeitüberschreitung anhält, da das Aufrufen .reject()eines bereits gelösten Versprechens nichts bewirkt.
Mark Amery

1
Obwohl die 'Abruf'-Funktion durch Timeout abgelehnt wird, wird die Hintergrund-TCP-Verbindung nicht geschlossen. Wie kann ich meinen Knotenprozess ordnungsgemäß beenden?
Prog Quester

26
HALT! Dies ist eine falsche Antwort! Es sieht zwar nach einer guten und funktionierenden Lösung aus, aber tatsächlich wird die Verbindung nicht geschlossen, was schließlich eine TCP-Verbindung belegt (kann sogar unendlich sein - hängt vom Server ab). Stellen Sie sich vor, diese FALSCHE Lösung soll in einem System implementiert werden, das jedes Mal eine Verbindung wiederholt. Dies kann dazu führen, dass die Netzwerkschnittstelle erstickt (überlastet) und Ihr Computer schließlich hängen bleibt! @Endless veröffentlicht die richtige Antwort hier .
Slavik Meltser

1
@ SlaikMeltser Ich verstehe es nicht. Die Antwort, auf die Sie hingewiesen haben, unterbricht auch nicht die TCP-Verbindung.
Mateus Pires

142

Ich mag den sauberen Ansatz von diesem Kern mit Promise.race sehr

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
Dies führt zu einer "nicht behandelten Ablehnung", wenn nach dem Timeout ein fetchFehler auftritt . Dies kann behoben werden, indem der Fehler behandelt ( ) und erneut ausgelöst wird, wenn das Timeout noch nicht aufgetreten ist. .catchfetch
Lionello

5
IMHO könnte dies mit AbortController beim Ablehnen weiter verbessert werden, siehe stackoverflow.com/a/47250621 .
RiZKiT

Es ist besser, das Timeout zu löschen, wenn der Abruf ebenfalls erfolgreich ist.
Bob9630

103

Mit dem AbortController können Sie Folgendes tun:

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

14
Dies sieht sogar noch besser aus als die Versprechen-Rennen-Lösung, da die Anfrage wahrscheinlich abgebrochen wird, anstatt nur die frühere Antwort zu erhalten. Korrigiere mich, wenn ich falsch liege.
Karl Adler

3
Die Antwort erklärt nicht, was AbortController ist. Außerdem ist es experimentell und muss in nicht unterstützten Engines polygefüllt werden. Außerdem ist es keine Syntax.
Estus Flask

Es erklärt möglicherweise nicht, was AbortController ist (ich habe der Antwort einen Link hinzugefügt, um es den Faulen zu erleichtern), aber dies ist die bisher beste Antwort, da sie die Tatsache hervorhebt, dass das bloße Ignorieren einer Anfrage nicht bedeutet, dass sie immer noch besteht nicht ausstehend. Gute Antwort.
Aurelio

2
"Ich habe der Antwort einen Link hinzugefügt, um es den Faulen zu erleichtern" - es sollte wirklich einen Link und mehr Informationen gemäß den Regeln enthalten. Aber danke, dass Sie die Antwort verbessert haben.
Jay Wick

6
Es ist besser, diese Antwort zu haben als keine Antwort, weil die Leute von Nitpickery abgeschreckt werden, tbh
Michael Terry

21

Aufbauend auf der hervorragenden Antwort von Endless habe ich eine hilfreiche Utility-Funktion erstellt.

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. Wenn das Zeitlimit erreicht ist, bevor die Ressource abgerufen wird, wird der Abruf abgebrochen.
  2. Wenn die Ressource vor Erreichen des Zeitlimits abgerufen wird, wird das Zeitlimit gelöscht.
  3. Wenn das Eingangssignal abgebrochen wird, wird der Abruf abgebrochen und das Timeout gelöscht.
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
        }
    });

Hoffentlich hilft das.


7

Es gibt noch keine Timeout-Unterstützung in der Abruf-API. Aber es könnte erreicht werden, indem man es in ein Versprechen einwickelt.

für zB.

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

Ich mag dieses besser, weniger repetitiv, um mehr als einmal zu verwenden.
Dandavis

1
Die Anfrage wird nach dem Timeout hier nicht storniert, richtig? Dies mag für das OP in Ordnung sein, aber manchmal möchten Sie eine Anfrage clientseitig abbrechen.
Tryse

2
@ Trysis gut, ja. Kürzlich wurde eine Lösung für das Abrufen von Abbrüchen mit AbortController implementiert , die jedoch immer noch experimentell mit eingeschränkter Browserunterstützung ist. Diskussion
Code-Jaff

Das ist lustig, IE & Edge sind die einzigen, die es unterstützen! Es sei denn, die mobile Mozilla-Site
funktioniert

Firefox unterstützt es seit 57. :: Anschauen bei Chrome ::
Franklin Yu

7

BEARBEITEN : Die Abrufanforderung wird weiterhin im Hintergrund ausgeführt und protokolliert höchstwahrscheinlich einen Fehler in Ihrer Konsole.

In der Tat ist der Promise.raceAnsatz besser.

Siehe diesen Link als Referenz Promise.race ()

Rennen bedeutet, dass alle Versprechen gleichzeitig ausgeführt werden und das Rennen beendet wird, sobald eines der Versprechen einen Wert zurückgibt. Daher wird nur ein Wert zurückgegeben . Sie können auch eine Funktion übergeben, die aufgerufen werden soll, wenn beim Abrufen eine Zeitüberschreitung auftritt.

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

Wenn dies Ihr Interesse weckt, wäre eine mögliche Implementierung:

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

2

Sie können einen timeoutPromise-Wrapper erstellen

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

Sie können dann jedes Versprechen einpacken

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

Eine zugrunde liegende Verbindung wird nicht abgebrochen, aber Sie können ein Versprechen auslaufen lassen.
Referenz


1

Wenn Sie in Ihrem Code kein Zeitlimit konfiguriert haben, ist dies das Standard-Zeitlimit für Anforderungen Ihres Browsers.

1) Firefox - 90 Sekunden

Geben Sie das about:configFirefox-URL-Feld ein. Suchen Sie den Wert, der dem Schlüssel entsprichtnetwork.http.connection-timeout

2) Chrome - 300 Sekunden

Quelle


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

Dies ist so ziemlich das gleiche wie stackoverflow.com/a/46946588/1008999, aber Sie haben eine Standardzeitüberschreitung
Endless

0

Mit c-versprechen2 lib könnte der stornierbare Abruf mit Zeitüberschreitung wie folgt aussehen ( Live-jsfiddle-Demo ):

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

Dieser Code als npm-Paket cp-fetch

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.