Heim >Web-Frontend >js-Tutorial >Chunk-Busters: Überqueren Sie nicht die Streams!

Chunk-Busters: Überqueren Sie nicht die Streams!

Linda Hamilton
Linda HamiltonOriginal
2024-12-02 05:00:10843Durchsuche

⚠️ Wenn Sie lichtempfindlich sind, möchten Sie dies wahrscheinlich überspringen.
Sehen Sie sich das statische Bild unten an. Die Lichter beginnen ganz schnell zu blinken!

Chunk-Busters: Don’t cross the Streams!

Wie funktioniert das Internet?

Denken Sie an den Titel ... wir sprechen hier von Streams.

Ich könnte über Protokolle, Pakete, Reihenfolge, Acks und Nacks sprechen … aber wir reden hier über Streams, und wie Sie wahrscheinlich richtig vermutet haben (ich glaube an Sie =D), sind Streams entweder binär oder Strings.

Ja, Strings werden gezippt, bevor sie gesendet werden … aber was uns in der Front- und Backend-Entwicklung normalerweise wichtig ist … Strings und Binärdateien.

In den folgenden Beispielen verwende ich JS-Streams.

Während Node über eigene Legacy-Implementierungen verfügt, haben wir Möglichkeiten, mit Streams umzugehen, die den gleichen Code haben, sei es vorne oder hinten.

Andere Sprachen haben ihre eigene Art, mit Streams umzugehen, aber wie Sie sehen werden … war der eigentliche Code-Teil des Umgangs damit nicht so kompliziert (um nicht zu sagen, dass hier keine komplexen Dinge passieren).

Das Beispielproblem

Sie haben ein Frontend, das Daten aus mehreren Quellen verarbeiten muss.

Während Sie auf jede Quelle einzeln über ihre IP/ihren Port zugreifen können, platzieren Sie sie zur einfacheren Verwendung und Steuerung hinter einem API-Gateway.

Das Repo

Schauen Sie sich das Repo unter dem Link an. Dort erfahren Sie, wie Sie es selbst ausführen, damit Sie damit spielen können.

https://github.com/Noriller/chunk-busters

Das Video

Videoversion zum Mitmachen:

https://youtu.be/QucaOfFI0fM

v0 – die naive Implementierung

Sie haben die Quellen, Sie holen sie ab, warten und rendern. Spülen und wiederholen.

await fetch1();
handleResult(1);
await fetch2();
handleResult(2);
...
await fetch9();
handleResult(9);

Sie denken vielleicht, dass das niemand wirklich tun wird …

In diesem Beispiel ist klar, dass etwas nicht stimmt, aber es ist nicht so schwer, darauf hereinzufallen.

Das Offensichtliche: Es ist langsam. Sie müssen jede Anfrage auslösen und warten, und wenn sie langsam ist, müssen Sie warten.

v1 – die eifrige Version

Sie wissen, dass Sie nicht auf jede einzelne Anfrage warten möchten … also lösen Sie alle aus und warten dann, bis sie abgeschlossen sind.

await Promise.all([
  fetch1(),
  fetch2(),
  ...
  fetch9(),
]);
handleAllResults(results);

Das ist es, was Sie wahrscheinlich tun, also ist es gut, oder?

Ich meine, es sei denn, Sie haben EINE einzige Anfrage, die langsam ist … das würde bedeuten, dass Sie, selbst wenn alle anderen bereits erledigt sind, immer noch warten müssten, bis diese abgeschlossen ist.

v2 – die intelligentere, eifrige Version

Sie wissen, dass Sie möglicherweise einige Anfragen haben, die langsamer sind, also lösen Sie immer noch alle aus und warten, aber wenn sie eingehen, machen Sie, wenn möglich, bereits etwas mit dem Ergebnis, sodass die anderen bereits fertig sind, wenn die letzte eintrifft.

await fetch1();
handleResult(1);
await fetch2();
handleResult(2);
...
await fetch9();
handleResult(9);

Das MUSS die beste Lösung sein, oder?

Hmm...etwas Seltsames?

v3 – Ich habe dich angelogen … so sollte v1 aussehen

Erinnern Sie sich an Version 1? Ja... so sollte es aussehen:

Es stellt sich heraus, dass es eine Grenze dafür gibt, wie viele Verbindungen Sie mit genau demselben Endpunkt in http/1 haben können, und nicht nur das ... es ist browserabhängig und jeder Browser kann unterschiedliche Grenzen haben.

Vielleicht denken Sie, Sie könnten einfach http/2 verwenden und damit Schluss machen … aber selbst wenn dies eine gute Lösung wäre, müssen Sie sich immer noch mit mehreren Endpunkten im Frontend auseinandersetzen.

Gibt es dafür überhaupt eine gute Lösung?

v4 – Streams eingeben!

Lassen Sie uns v0 noch einmal betrachten, aber mit Streams …

Sie sind schlau, also haben Sie das wahrscheinlich erwartet, da die Warnung es ein wenig verfälscht hat … aber ja … was Sie vorher gesehen haben, waren nicht alle Daten, die das Backend generiert hat.

Jedenfalls ... während wir abrufen, rendern wir.

await Promise.all([
  fetch1(),
  fetch2(),
  ...
  fetch9(),
]);
handleAllResults(results);

Wenn wir stattdessen auf den kommenden Stream tippen, können wir mit den Datenblöcken sofort etwas anfangen. (Ja! Wie Chat GPT und ähnliches schon.)

Auch wenn v0 die schlechteste Lösung für dieses Problem darstellt, wird es durch die Verwendung von Streams erheblich verbessert. Sie können den Benutzer täuschen, indem Sie etwas, irgendetwas zeigen, auch wenn die Gesamtwartezeit gleich ist.

v5 - v1, wieder, aber mit Streams!

Das http/1-Problem ist immer noch ein Problem, aber auch hier kann man die Dinge bereits sehen, wie sie kommen.

Ja... ich kann das nicht länger hinauszögern... also...

v6 – eine API, um sie alle zu beherrschen!

Oder... vielleicht kann ich?

Sehen Sie, das Frontend musste zu viel verwalten … wenn wir das auf das Backend auslagern können, können Sie einen Endpunkt haben, der alle Quellen verwaltet.

Dies löst die Komplexität der Frontend- und http/1-Probleme.

await Promise.all([
  fetch1().then(handleResult),
  fetch2().then(handleResult),
  ...
  fetch9().then(handleResult),
]);


// usually we do this:
await fetch(...).then((res) => {
  // this json call accumulate all the response
  // that later is returned for you to use
  return res.json()
})

v7 – und schließlich… eine API, mehrere Quellen und Streaming.

Wir rufen eine API auf, die alle Quellen aufruft, die Daten streamt, verarbeitet und an die Front weiterleitet, die wiederum die Daten so rendert, wie sie kommen.

Der hierfür verwendete Code ist auf Vorder- und Rückseite grundsätzlich derselbe:

await fetchAll();
handleAllResults(results);

Ja… das ist es (wie das einfachste und einfachste Beispiel sagt).

Wir fügen die kommende Zeichenfolge einem Puffer hinzu, analysieren sie, prüfen, ob es einen verwendbaren Block gibt, verwenden ihn und vergessen ihn. Das bedeutet, dass Sie TBs an Daten empfangen/verbrauchen könnten … einen Block nach dem anderen, mit wenig RAM.

Ich weiß, was du denkst... und es ist dumm... es ist auch Wahnsinn...

MOOOOOOOM Ich will Websockets!

Nein Schatz, wir haben WebSockets zu Hause!

Websockets zu Hause: Weiter?

v8 – es ist nur dumm, wenn es nicht funktioniert

Sie sind schlau, Sie dachten, wenn die Quelle immer noch Daten generiert, könnten wir vielleicht einige Variablen aktualisieren...

Auf diese Weise können Sie die eine Verbindung beibehalten, um mehr Daten abzurufen oder etwas an dem zu ändern, was sie generiert.

Ja… ich schätze, das könntest du tun… und ich habe das Beispiel auf dein Drängen gemacht. =D

Trotzdem… es ist eine dumme Idee, ich weiß nicht, wo/ob sie in einer echten Produktionsumgebung verwendet werden kann. Vielleicht reisen Sie in die Zeit zurück in die unangenehme JS-Phase zwischen MPA und Ajax, in der Sie zwar genügend Interaktivität, aber nicht genügend Verbindungen zum selben Server hatten (einige Browser hatten ein Limit von nur 2!), dann vielleicht?

Ansonsten keine Ahnung. Wenn ja, lassen Sie es mich wissen.

Achten Sie im obigen Beispiel auf die Mitteltafel, insbesondere auf die „Fortschrittsgrenze“: Sie können sehen, dass sie ständig aktualisiert wird. Wenn Sie die Registerkarte „Netzwerk“ öffnen, sehen Sie, dass die GET-Verbindung nie vor dem Ende geschlossen wird. Sie würden auch mehrere andere Anfragen sehen, die ändern, was diese eine, noch aktive Verbindung tat … und das alles mit Vanilla http/1.

Was kommt als nächstes?

String vs. JSON

Dieses Beispiel ist das einfachste, das ich machen konnte. Ich verwende sogar einfache Zeichenfolgen anstelle von JSON, da diese einfacher zu analysieren sind.

Um JSON zu verwenden, müssen Sie die Zeichenfolge akkumulieren (wir müssen die Backend-Antwort aus einem bestimmten Grund JSON.stringifizieren).

Überprüfen Sie dann entweder, wo es gebrochen werden soll, und analysieren Sie dann diesen Wert oder analysieren Sie es, während Sie fortfahren.

Denken Sie beim ersten Fall an NDJSON: Anstelle eines JSON-Arrays trennen Sie die Objekte durch neue Zeilen, dann könnten Sie „einfacher“ finden, wo sie umbrechen müssen, dann jedes JSON.parsen und das Objekt verwenden.

Für Letzteres analysieren Sie im Laufe der Zeit wie folgt: Sie wissen, dass Sie sich in einem Array befinden, jetzt ist es ein Objekt, ok, erster Schlüssel, jetzt ist es der Wert des Schlüssels, nächster Schlüssel, überspringen Sie das, nächster Schlüssel ... und so weiter und so weiter … es ist nicht trivial, es manuell zu machen, aber es ist wie der Sprung vom „Warten, dann Rendern“ zum „Rendern, während Sie warten“, worum es geht … außer … in einem noch kleineren Maßstab.

Fehlerbehandlung

Die Leute hosten gerne Beispiele, dieses müssen Sie selbst ausführen ... Ich hoffe, der Grund dafür, dass Sie das Beispiel nicht irgendwo hosten, ist jetzt klar, aber ein weiterer Grund ist, dass wir hier keinen Fehler erwarten, und wenn Sie das tun würden Fügen Sie vor allem Netzwerkfehler hinzu … na ja …

Fehler sollten behandelt werden, aber sie erhöhen die Komplexität um eine weitere Ebene.

Sollten Sie es verwenden?

Vielleicht...man kann es sagen hängt davon ab

Es gibt Orte, an denen Streaming die Antwort ist, aber in den meisten Fällen reicht … Warten auf JSON aus (ganz zu schweigen davon, dass es einfacher ist).

Aber das Erlernen von Streams eröffnet Möglichkeiten, einige Probleme zu lösen, sei es im Frontend oder im Backend.

Im Frontend können Sie dies jederzeit nutzen, um den Benutzer „auszutricksen“. Anstatt überall Spinner zu zeigen, können Sie etwas so zeigen, wie es kommt, und dann mehr zeigen, wie es kommt, auch wenn es eine Weile dauert. Solange Sie den Benutzer nicht daran hindern, damit zu interagieren, können Sie sogar etwas erstellen, das „langsamer“ ist, als nur den Spinnern das Gefühl zu geben, dass es viel schneller ist als alles, was eigentlich schneller ist . Im Backend können Sie RAM sparen, da Sie jeden Datenblock einfach so analysieren können, wie er kommt, sei es von vorne, einer Datenbank oder irgendetwas anderem dazwischen. Behandeln Sie die Daten nach Bedarf und senden Sie sie, ohne auf die gesamte Nutzlast warten zu müssen, was zu einem OOM-Fehler (Out of Memory) führen würde. GB oder sogar TB an Daten … klar, warum nicht?

Outro

Ist React langsam? Dieses gesamte Beispiel-Frontend wurde mit React erstellt und abgesehen von der „Hauptsache“ mit all den blinkenden „Lichtern“ passieren noch viele andere Dinge.

Ja... wenn Sie schnell genug vorgehen, kann das Beispiel nicht mithalten und fängt an einzufrieren. Aber da pro Minute problemlos Tausende von Renderings ausgeführt werden, denke ich, dass es für die meisten Anwendungen ausreicht.

Und Sie können die Leistung jederzeit verbessern: Für die „Fortschrittsgrenze“ habe ich verzögerte Werte verwendet, um sie glatter zu machen, wenn Sie beim Rendern etwas sparen müssen … Ich könnte diese und andere Leistungsverbesserungen für die „Lichter“ vornehmen. und der Titel, aber es würde einfach dazu führen, dass die „Lichter“ oft aufhören zu blinken (was keine schöne Demo wäre), und auch die „elektrische“ Unterstreichung im Titel würde nicht so viel Spaß machen wie sie ist.

In diesem Beispiel wären all diese „Verbesserungen“ nicht ideal, aber für normale Anwendungen ... können Sie damit einiges bewältigen. Und wenn Sie doch noch etwas mehr benötigen, dann nutzen Sie in diesem Fall eine andere Lösung.

Abschluss

Fügen Sie Streams zu Ihrem Arsenal hinzu … es ist vielleicht keine Allheilmittellösung, aber eines Tages wird es sich sicherlich als nützlich erweisen.

Und wenn Sie etwas damit anfangen wollen und Hilfe brauchen, dann rufen Sie mich vielleicht an. =P

Das obige ist der detaillierte Inhalt vonChunk-Busters: Überqueren Sie nicht die Streams!. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
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