Heim > Web-Frontend > CSS-Tutorial > Senden Sie zuverlässig eine HTTP -Anforderung, wenn ein Benutzer eine Seite verlässt

Senden Sie zuverlässig eine HTTP -Anforderung, wenn ein Benutzer eine Seite verlässt

William Shakespeare
Freigeben: 2025-03-14 10:06:09
Original
468 Leute haben es durchsucht

Senden Sie zuverlässig eine HTTP -Anforderung, wenn ein Benutzer eine Seite verlässt

In vielen Fällen muss ich eine HTTP -Anfrage senden, die einige Daten enthält, um aufzuzeichnen, was der Benutzer tut, z. B. das Navigieren auf eine andere Seite oder das Senden eines Formulars. Betrachten Sie dieses Beispiel, in dem einige Informationen an einen externen Dienst sendet, wenn Sie auf einen Link klicken:

 <a href="https://www.php.cn/link/3cbfb2330b21840b385a45c958602663">Gehen Sie zur Seite</a>

document.getElementById ('link'). addEventListener ('klick', (e) => {
  fetch ("/log", {
    Methode: "Post",
    Header: {
      "Inhaltstyp": "Anwendung/JSON"
    }, 
    Körper: Json.Stringify ({{
      Einige: "Daten"
    })
  });
});
Nach dem Login kopieren

Hier gibt es nichts besonders kompliziertes. Der Link funktioniert wie gewohnt (ich habe nicht e.preventDefault() verwendet), aber bevor dieses Verhalten auftritt, wird eine Postanforderung ausgelöst. Sie müssen nicht auf eine Antwort warten. Ich möchte es nur an einen Service senden, den ich besuche.

Auf den ersten Blick könnten Sie der Meinung sind, dass die Planung der Anfrage synchronisiert wird. Danach werden wir weiterhin von der Seite navigieren und andere Server die Anfrage erfolgreich verarbeiten. Es stellt sich jedoch heraus, dass dies nicht immer der Fall ist.

Der Browser garantiert nicht, dass offene HTTP -Anfragen beibehalten werden

Wenn bestimmte Ereignisse auftreten, um eine Seite im Browser zu beenden, gibt es keine Garantie dafür, dass die verarbeitete HTTP -Anfrage erfolgreich ist (mehr zum Lebenszyklus der Seite "Terminierung" und anderer Zustände). Die Zuverlässigkeit dieser Anforderungen kann von einer Reihe von Faktoren abhängen - der Netzwerkverbindung, der Anwendungsleistung und sogar der Konfiguration externer Dienste selbst.

Daher kann das Senden von Daten in diesen Momenten alles andere als zuverlässig sein. Wenn Sie sich auf diese Protokolle verlassen, um datenempfindliche Geschäftsentscheidungen zu treffen, kann es ein potenziell erhebliches Problem geben.

Um diese Unzuverlässigkeit zu veranschaulichen, habe ich eine kleine Express -Anwendung mit einer Seite mit dem obigen Code eingerichtet. Wenn der Link angeklickt wird, navigiert der Browser zu /other , aber bevor dies geschieht, wird eine Postanforderung ausgestellt.

Während alles passiert, öffne ich die Registerkarte "Netzwerk" meines Browsers und verwende die Verbindungsgeschwindigkeit "Slow 3G". Nachdem die Seite geladen wurde, habe ich die Protokolle gelöscht und alles sieht ruhig aus:

Aber sobald Sie auf den Link klicken, gehen die Dinge schief. Bei der Navigation wird die Anfrage storniert.

Dadurch können wir fast nicht sicher sein, dass externe Dienste die Anfrage tatsächlich behandeln können. Um dieses Verhalten zu überprüfen, geschieht dies auch, wenn wir window.location verwenden.

 document.getElementById ('link'). addEventListener ('klick', (e) => {
  E.PreventDefault ();

  // Die Anfrage wurde in die Warteschlange gestellt, wurde jedoch unmittelbar nach der Navigation storniert.
  fetch ("/log", {
    Methode: "Post",
    Header: {
      "Inhaltstyp": "Anwendung/JSON"
    },
    Körper: Json.Stringify ({{
      Einige: "Daten"
    }),
  });

  window.location = e.target.href;
});
Nach dem Login kopieren

Diese unvollendeten Anfragen können aufgegeben werden, unabhängig davon, wie oder wann die Navigation auftritt und wann der aktive Seiten endet.

Warum wurde es abgesagt?

Die Hauptursache des Problems ist, dass XHR-Anfragen (über fetch oder XMLHttpRequest ) standardmäßig asynchron und nicht blockiert sind. Sobald die Anfrage in der Warteschlange gestellt wurde, wird die tatsächliche Arbeit der Anfrage an die API auf Browser-Ebene im Hintergrund weitergegeben.

Dies ist in Bezug auf die Leistung großartig - Sie möchten nicht, dass die Anfrage den Haupt -Thread aufnimmt. Dies bedeutet jedoch auch, dass Seiten, wenn sie in den "Kündigungsstaat" eintreten, das Risiko ausgesetzt ist, aufgegeben zu werden, und es gibt keine Garantie dafür, dass Hintergrundarbeiten abgeschlossen werden können. Hier ist die Zusammenfassung von Google dieses bestimmten Lebenszykluszustands:

Sobald die Seite mit der Deinstallation beginnt und vom Browser aus dem Speicher gelöscht wird, befindet sie sich in einem abgelegten Zustand. In diesem Zustand können keine neuen Aufgaben gestartet werden und können beendet werden, wenn die laufende Aufgabe zu lange ausgeführt wird.

Kurz gesagt, die Entwurfsannahme des Browsers ist, dass bei der Schließung einer Seite keine weiteren Verarbeitung von Hintergrundprozessen erforderlich ist.

Also, was sind unsere Entscheidungen?

Der offensichtlichste Weg, dieses Problem zu vermeiden, besteht darin, die Benutzeraktion so weit wie möglich zu verzögern, bis die Anfrage eine Antwort zurückgibt. In der Vergangenheit wurde dies fälschlicherweise unter Verwendung der Synchronisationsflags erfolgt, die in XMLHttpRequest unterstützt wurden. Aber die Verwendung des Hauptfadens blockiert vollständig und verursachte viele Leistungsprobleme - ich habe in der Vergangenheit ein paar Dinge darüber geschrieben -, sodass diese Idee nicht einmal in Betracht gezogen werden sollte. Tatsächlich ist es kurz davor, die Plattform zu verlassen (Chrome V80 hat sie bereits entfernt).

Wenn Sie diesen Ansatz verfolgen möchten, ist es besser, auf das Versprechen zu warten, die zurückgegebene Antwort zu lösen. Sobald Sie zurückkehren, können Sie das Verhalten sicher ausführen. Mit unserem vorherigen Code -Snippet könnte dies so aussehen:

 document.getElementById ('link'). addEventListener ('klick', async (e) => {
  E.PreventDefault ();

  // Warten Sie auf die Antwort auf die Rückkehr ...
  Warten Sie fetch ("/log", {
    Methode: "Post",
    Header: {
      "Inhaltstyp": "Anwendung/JSON"
    },
    Körper: Json.Stringify ({{
      Einige: "Daten"
    }),
  });

  //… und dann navigieren, um zu gehen.
  window.location = e.target.href;
});
Nach dem Login kopieren

Dies kann den Job machen, hat aber einige nicht triviale Nachteile.

Erstens wirkt sich dies auf die Benutzererfahrung aus, indem es das Auftreten des erforderlichen Verhaltens verzögert. Das Sammeln von Analysedaten ist für das Unternehmen (und für zukünftige Benutzer) sicherlich von Vorteil, aber das ist alles andere als ideal, da die aktuellen Benutzer für die Erreichung dieser Vorteile zahlen. Ganz zu schweigen von einer externen Abhängigkeit, die Latenz oder andere Leistungsprobleme im Dienst selbst werden dem Benutzer widerspiegelt. Wenn eine Auszeit Ihres Analysedienstes dazu führt, dass der Kunde einen hochwertigen Betrieb nicht ausführt, wird jeder verlieren.

Zweitens ist dieser Ansatz nicht so zuverlässig, wie er anfänglich klingt, da ein Terminierungsverhalten nicht programmatisch verzögert werden kann. Beispielsweise ist e.preventDefault() nutzlos, wenn es darum geht, den Benutzer zu verzögern, um die Registerkarte Browser zu schließen. Bestenfalls wird es im besten Fall die Sammlung von Daten von bestimmten Benutzeraktionen abdecken, aber nicht genug, um ihr vollständig zu vertrauen.

Weisen Sie den Browser an, unvollendete Anfragen zu behalten

Zum Glück haben die meisten Browser integrierte Optionen , um unvollendete HTTP-Anfragen beizubehalten , ohne die Benutzererfahrung zu beeinträchtigen.

Verwenden Sie Fetchs Keepalive -Logo

Wenn das keepalive -Flag bei Verwendung fetch() auf true eingestellt ist, bleibt die entsprechende Anforderung offen, auch wenn die Seite, die die Anforderung initiiert hat, beendet wurde. Mit unserem ersten Beispiel wird die Implementierung wie folgt aussehen:

 <a href="https://www.php.cn/link/3cbfb2330b21840b385a45c958602663">Gehen Sie zur Seite</a>

document.getElementById ('link'). addEventListener ('klick', (e) => {
  fetch ("/log", {
    Methode: "Post",
    Header: {
      "Inhaltstyp": "Anwendung/JSON"
    },
    Körper: Json.Stringify ({{
      Einige: "Daten"
    }),
    Keepalive: wahr
  });
});
Nach dem Login kopieren

Beim Klicken auf den Link und die Seitennavigation treten keine Anfrage ab, die Stornierung erfolgt:

Stattdessen erhalten wir einen (unbekannten) Zustand, nur weil die aktive Seite nie darauf wartet, dass eine Antwort empfangen wird.

Ein solcher Einzelzeilencode ist einfach zu beheben, insbesondere wenn er Teil einer gemeinsamen Browser-API ist. Wenn Sie jedoch nach einer zentraleren Option mit einer einfacheren Schnittstelle suchen, gibt es eine andere Möglichkeit, dies mit nahezu gleicher Browser -Unterstützung zu tun.

Verwenden Sie Navigator.SendBeacon ()

Navigator.sendBeacon() wird speziell zum Senden von Einweg-Anfragen (Beacons) verwendet. Eine grundlegende Implementierung ist wie folgt, die einen Beitrag mit einem strittigen JSON und einem "Text/Plain" Content-Type sendet:

 navigator.sendBeacon ('/log', json.stringify ({{{{
  Einige: "Daten"
}));
Nach dem Login kopieren

Mit dieser API können Sie jedoch nicht benutzerdefinierte Headers senden. Um unsere Daten als "Anwendung/JSON" zu senden, müssen wir eine kleine Änderung vornehmen und den Blob verwenden:

 <a href="https://www.php.cn/link/3cbfb2330b21840b385a45c958602663">Gehen Sie zur Seite</a>

document.getElementById ('link'). addEventListener ('klick', (e) => {
  const blob = neuer Blob ([json.stringify ({einige: "data"})], {type: 'application/json; charSet = utf-8'});
  Navigator.SendBeacon ('/log', Blob);
});
Nach dem Login kopieren

Letztendlich erzielten wir das gleiche Ergebnis - die Anfrage wurde auch nach der Navigation der Seite abgeschlossen. Aber es gibt einige Dinge, die es geschehen, was es besser machen kann als fetch() : Beacons werden mit niedriger Priorität gesendet.

Zur Demonstration wird hier hier auf der Registerkarte Netzwerk angezeigt, wenn sowohl fetch() als auch sendBeacon() mit keepalive verwendet werden:

Standardmäßig erhält fetch() eine "hohe" Priorität, während Beacons (oben als "Ping" -Typ eine "niedrigste" Priorität haben. Dies ist eine gute Sache für Anfragen, die für die Funktionalität der Seite nicht wichtig sind. Direkt aus der Beacon -Spezifikation:

Diese Spezifikation definiert eine Schnittstelle, die […] den Ressourcenwettbewerb mit anderen zeitkritischen Operationen minimiert und gleichzeitig sicherstellt, dass solche Anforderungen noch verarbeitet und an das Ziel geliefert werden.

Mit anderen Worten, sendBeacon() stellt sicher, dass seine Anfragen keine Anfragen behindern, die für Ihre Anwendung und Benutzererfahrung wirklich wichtig sind.

Ehrendienst von Ping -Attributen

Es ist erwähnenswert, dass immer mehr Browser ping -Attribute unterstützen. Bei Anhang an den Link gibt es eine kleine Postanforderung aus:

<a href="https://www.php.cn/link/fef56cae0dfbabedeadb64bf881ab64f" ping="http://localhost:3000/log">
  Gehen Sie zu einer anderen Seite
</a>
Nach dem Login kopieren

Diese Anfrage-Header enthalten die Seite, auf der der Link geklickt wird (Ping-from) und den HREF-Wert des Links (Ping-to):

 <code>headers: { 'ping-from': 'http://localhost:3000/', 'ping-to': 'https://www.php.cn/link/fef56cae0dfbabedeadb64bf881ab64f' 'content-type': 'text/ping' // ...其他标头},</code>
Nach dem Login kopieren

Es ähnelt technisch dem Senden von Beacons, hat aber einige bemerkenswerte Einschränkungen:

  1. Es ist streng beschränkt auf die Verwendung für Links, was eine schlechte Wahl wäre, wenn Sie Daten im Zusammenhang mit anderen Interaktionen wie Schaltflächenklicks oder Formulareinführungen verfolgen müssen.
  2. Der Browser unterstützt gut, aber nicht großartig. Zum Zeitpunkt des Schreibens aktiviert Firefox es ausdrücklich nicht standardmäßig.
  3. Sie können keine benutzerdefinierten Daten mit der Anfrage senden. Wie bereits erwähnt, können Sie höchstens ein paar Ping-*-Header und andere Header erhalten.

Alles in allem ist ping ein großartiges Tool, wenn Sie nur einfache Anfragen senden und keine benutzerdefinierten JavaScript schreiben möchten. Wenn Sie jedoch mehr Inhalte senden müssen, ist dies möglicherweise nicht die beste Option.

Also, welches soll ich wählen?

Es gibt sicherlich einen Kompromiss beim Senden der Anfrage der letzten Sekunden mit fetch() oder sendBeacon() mit keepalive . Um zu sagen, welcher Ansatz für verschiedene Situationen am besten geeignet ist, finden Sie hier einige Dinge:

Sie können in den folgenden Fällen Fetch () Keepalive wählen:

  • Sie müssen benutzerdefinierte Header einfach mit Anfragen übergeben.
  • Sie möchten eine Get -Anfrage an den Dienst ausstellen, nicht an eine Postanfrage.
  • Sie unterstützen ältere Browser (wie IE) und haben Polyfill geladen.

SendBeacon () ist jedoch in den folgenden Fällen eine bessere Option:

  • Sie stellen einfache Serviceanfragen vor, die nicht viel Anpassung erfordern.
  • Sie bevorzugen eine prägnantere und elegantere API.
  • Sie möchten sicherstellen, dass Ihre Anfrage nicht mit anderen in der Bewerbung gesendeten Anfragen mit hoher Priorität konkurriert.

Vermeiden Sie es, die gleichen Fehler zu wiederholen

Es gibt einen Grund, warum ich mich entschieden habe, mich tief in die Art und Weise zu befassen, wie der Browser in den In-Prozess-Anfragen umgeht, wenn die Seite endet. Nachdem wir vor kurzem, nachdem wir angefangen hatten, Anfragen sofort zu entlassen, als wir das Formular eingereicht hatten, stellte unser Team fest, dass sich die Häufigkeit bestimmter Arten von analytischen Protokollen plötzlich änderte. Diese Veränderung ist plötzlich und signifikant - rund 30% gegenüber dem, was wir in der Geschichte gesehen haben.

Eingraben in die Ursache dieses Problems und die verfügbaren Werkzeuge, um das Wiederer auftauchen, das das Problem am Tag gerettet wurde. Wenn überhaupt, hoffe ich, die Nuancen dieser Herausforderungen zu verstehen, jemandem etwas von den Schmerzen zu vermeiden, denen wir begegnen. Happy Record!

Das obige ist der detaillierte Inhalt vonSenden Sie zuverlässig eine HTTP -Anforderung, wenn ein Benutzer eine Seite verlässt. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Neueste Artikel des Autors
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage