Das ursprüngliche GitHub-Problem für "Einen Abruf abbrechen" war seit 2015. Wenn ich jetzt 2015 von 2017 (dem aktuellen Jahr) abziehe, bekomme ich 2. Dies zeigt eine Fehler in der Mathematik, denn 2015 war in Wirklichkeit zurück.
2015 begannen wir damit, den Abbruch laufender Abrufe zu untersuchen, und nach 780 GitHub-Kommentaren Nach ein paar Fehlstarts und 5 Pull-Anfragen gibt es nun endlich eine Abbruchoption für den Abruf in Browsern. wobei das erste Firefox 57 ist.
Update:Nein, ich lag falsch. Edge 16 ist zuerst mit Unterstützung zum Abbrechen gelandet. Herzlichen Glückwunsch an Edge-Team!
Wir sehen uns den Verlauf später noch genauer an. Zuerst möchte ich Ihnen jedoch die API vorstellen:
Steuerung und Signalbewegung
Wir stellen vor: AbortController
und AbortSignal
:
const controller = new AbortController();
const signal = controller.signal;
Der Controller hat nur eine Methode:
controller.abort();
In diesem Fall wird das Signal gesendet:
signal.addEventListener('abort', () => {
// Logs true:
console.log(signal.aborted);
});
Diese API wird durch den DOM-Standard bereitgestellt. Das ist die gesamte API. Es ist bewusst allgemein sein, damit sie auch von anderen Webstandards und JavaScript-Bibliotheken verwendet werden können.
Signale abbrechen und Daten abrufen
Der Abruf kann AbortSignal
dauern. Hier sehen Sie zum Beispiel, wie Sie ein Zeitlimit für den Abruf nach 5 Tagen festlegen,
Sekunden:
const controller = new AbortController();
const signal = controller.signal;
setTimeout(() => controller.abort(), 5000);
fetch(url, { signal }).then(response => {
return response.text();
}).then(text => {
console.log(text);
});
Wenn Sie einen Abruf abbrechen, werden sowohl die Anfrage als auch die Antwort abgebrochen, sodass jedes Lesen des Antworttexts
(z. B. response.text()
) wird ebenfalls abgebrochen.
Hier finden Sie eine Demo: Zum Zeitpunkt der Entstehung dieses Artikels war der einzige Browser, unterstützt Firefox 57. Machen Sie sich bereit, es war niemand mit Designkenntnissen beteiligt. bei der Erstellung der Demo.
Alternativ kann das Signal an ein Anfrageobjekt übergeben und später zum Abrufen weitergeleitet werden:
const controller = new AbortController();
const signal = controller.signal;
const request = new Request(url, { signal });
fetch(request);
Das funktioniert, weil request.signal
ein AbortSignal
ist.
Auf abgebrochenen Abruf reagieren
Wenn du einen asynchronen Vorgang abbrichst, wird das Promise mit einem DOMException
namens AbortError
abgelehnt:
fetch(url, { signal }).then(response => {
return response.text();
}).then(text => {
console.log(text);
}).catch(err => {
if (err.name === 'AbortError') {
console.log('Fetch aborted');
} else {
console.error('Uh oh, an error!', err);
}
});
Es kommt selten vor, dass eine Fehlermeldung angezeigt wird, wenn der Nutzer den Vorgang abgebrochen hat, da es sich dabei nicht um "Fehler" wenn Sie dem Wunsch der Nutzenden erfolgreich folgen. Um dies zu vermeiden, verwenden Sie eine if-Anweisung wie die eines speziell für Abbruchfehler.
Das folgende Beispiel zeigt dem Nutzer eine Schaltfläche zum Laden von Inhalten und eine Schaltfläche zum Abbrechen. Wenn der Abruf Fehler wird ein Fehler angezeigt, es sei denn, es handelt sich um einen Abbruchfehler:
// This will allow us to abort the fetch.
let controller;
// Abort if the user clicks:
abortBtn.addEventListener('click', () => {
if (controller) controller.abort();
});
// Load the content:
loadBtn.addEventListener('click', async () => {
controller = new AbortController();
const signal = controller.signal;
// Prevent another click until this fetch is done
loadBtn.disabled = true;
abortBtn.disabled = false;
try {
// Fetch the content & use the signal for aborting
const response = await fetch(contentUrl, { signal });
// Add the content to the page
output.innerHTML = await response.text();
}
catch (err) {
// Avoid showing an error message if the fetch was aborted
if (err.name !== 'AbortError') {
output.textContent = "Oh no! Fetching failed.";
}
}
// These actions happen no matter how the fetch ends
loadBtn.disabled = false;
abortBtn.disabled = true;
});
Hier ist eine Demo. Zum Zeitpunkt der Entstehung dieses Artikels waren die einzigen Browser, die unterstützen Edge 16 und Firefox 57.
Ein Signal, viele Abrufe
Mit einem einzelnen Signal können mehrere Abrufe gleichzeitig abgebrochen werden:
async function fetchStory({ signal } = {}) {
const storyResponse = await fetch('/story.json', { signal });
const data = await storyResponse.json();
const chapterFetches = data.chapterUrls.map(async url => {
const response = await fetch(url, { signal });
return response.text();
});
return Promise.all(chapterFetches);
}
Im obigen Beispiel wird dasselbe Signal für den ersten Abruf und für das parallele Kapitel verwendet.
Abrufe. So würden Sie fetchStory
verwenden:
const controller = new AbortController();
const signal = controller.signal;
fetchStory({ signal }).then(chapters => {
console.log(chapters);
});
In diesem Fall werden durch den Aufruf von controller.abort()
alle laufenden Abrufe abgebrochen.
Die Zukunft
Andere Browser
Edge hat diese erste Version großartig bereitgestellt und Firefox ist auf der Suche. Ihre Entwickler in der Testsuite implementiert, während die Spezifikation geschrieben wird. Für andere Browser gibt es diese Tickets:
In einem Service Worker
Ich muss noch die Spezifikation für Service Worker-Teile fertigstellen, aber hier ist der Plan:
Wie bereits erwähnt, hat jedes Request
-Objekt ein signal
-Attribut. Innerhalb eines Service Workers
fetchEvent.request.signal
signalisiert „Abbrechen“, wenn die Seite nicht mehr an der Antwort interessiert ist.
Daher funktioniert Code wie dieser einfach:
addEventListener('fetch', event => {
event.respondWith(fetch(event.request));
});
Wird der Abruf durch die Seite abgebrochen, wird der Vorgang durch fetchEvent.request.signal
-Signale abgebrochen, sodass der Abruf innerhalb der
bricht auch der Service Worker ab.
Wenn Sie etwas anderes als event.request
abrufen, müssen Sie das Signal an Ihren
benutzerdefinierte Abrufe.
addEventListener('fetch', event => {
const url = new URL(event.request.url);
if (event.request.method == 'GET' && url.pathname == '/about/') {
// Modify the URL
url.searchParams.set('from-service-worker', 'true');
// Fetch, but pass the signal through
event.respondWith(
fetch(url, { signal: event.request.signal })
);
}
});
Folgen Sie dazu der Spezifikation, um dies nachzuverfolgen. Ich füge Links sobald die Implementierung bereit ist.
Die Geschichte
Ja... es hat lange gedauert, bis diese relativ einfache API entwickelt wurde. Das hat mehrere Gründe:
API-Widerspruch
Wie Sie sehen, ist die GitHub-Diskussion ziemlich lang.
In diesem Thread gibt es viele Nuancen und ein wenig Nuancen, aber der Hauptunterschied ist eine
Die Gruppe wollte, dass die Methode abort
in dem von fetch()
zurückgegebenen Objekt vorhanden sein soll, während die andere Methode
eine Trennung zwischen dem Erhalt der Antwort und der Beeinflussung der Antwort wünschten.
Diese Anforderungen sind nicht kompatibel, sodass eine Gruppe nicht das Gewünschte finden konnte. Wenn das
Tut mir leid! Wenn du dich dadurch besser fühlst, war ich auch in dieser Gruppe. Aber AbortSignal
als
Anforderungen anderer APIs scheint die richtige Wahl zu sein. Außerdem können verkettete Versprechen
abschreckbar zu werden, sehr kompliziert oder sogar unmöglich werden.
Wenn Sie ein Objekt zurückgeben wollten, das eine Antwort liefert, aber auch abbrechen kann, können Sie ein Einfacher Wrapper:
function abortableFetch(request, opts) {
const controller = new AbortController();
const signal = controller.signal;
return {
abort: () => controller.abort(),
ready: fetch(request, { ...opts, signal })
};
}
„False“ beginnt in TC39
Es wurde versucht, eine abgebrochene Aktion von einem Fehler zu unterscheiden. Dazu gehörte ein drittes Versprechen, Status, der für „abgebrochen“ steht, und eine neue Syntax für die Stornierung sowohl bei der Synchronisierung als auch im asynchronen Modus Code:
Kein echter Code – Angebot wurde zurückgezogen
try { // Start spinner, then: await someAction(); } catch cancel (reason) { // Maybe do nothing? } catch (err) { // Show error message } finally { // Stop spinner }
Die häufigste Maßnahme nach dem Abbruch einer Aktion ist nichts. Das obige Angebot wurde getrennt
Abbruchfehler beheben, sodass Sie sich nicht speziell mit Abbruchfehlern befassen mussten. catch cancel
mal
hören Sie von abgebrochenen Aktionen. In den meisten Fällen ist dies jedoch nicht erforderlich.
Dies erreichte im TC39 Phase 1, aber kein Konsens erreichte, und der Vorschlag wurde zurückgezogen.
Für unseren alternativen Vorschlag AbortController
war keine neue Syntax erforderlich und er ergab daher keinen Sinn.
um sie in TC39 zu spezifizieren. Alles, was wir von JavaScript benötigten, war bereits vorhanden, also haben wir die
Schnittstellen innerhalb der Webplattform, insbesondere DOM-Standard Nachdem wir diese Entscheidung getroffen hatten,
der Rest kam relativ schnell zusammen.
Große Änderung der Spezifikation
XMLHttpRequest
kann seit Jahren abgetrieben werden, aber die Angaben waren ziemlich vage. Es war nicht klar,
zu welchen Punkten die zugrunde liegende Netzwerkaktivität vermieden oder beendet werden könnte, oder was passiert ist, wenn
Es gab eine Race-Bedingung zwischen dem Aufruf von abort()
und dem Abschluss des Abrufs.
Dieses Mal wollten wir, dass alles richtig ist, aber das führte zu einer großen Spezifikationsänderung, die viel Zeit erforderte. (das ist meine Schuld. Vielen Dank an Anne van Kesteren und Domenic Denicola dafür, dass er mich durchzieht) und eine Reihe von Tests.
Aber wir sind jetzt hier! Es gibt ein neues Web-Primitive zum Abbrechen von asynchronen Aktionen. gleichzeitig gesteuert werden. Später werden wir uns ansehen, wie Prioritätsänderungen während der gesamten Dauer eines Abrufs aktiviert werden können. Außerdem wird eine übergeordnete Die API zur Beobachtung des Abruffortschritts.