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?

Akshay Lokur
quelle

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

Shakeel
quelle
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
})
Karl Adler
quelle
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);
})
Endlos
quelle
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.

Aadit M Shah
quelle
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);
      }
    });
  }
Code-Jaff
quelle
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;
  });
}
Arroganz
quelle
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

Pulkit Aggarwal
quelle
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

Harikrishnan
quelle
0
  fetchTimeout (url,options,timeout=3000) {
    return new Promise( (resolve, reject) => {
      fetch(url, options)
      .then(resolve,reject)
      setTimeout(reject,timeout);
    })
  }
Mojimi
quelle
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

Dmitriy Mozgovoy
quelle