Long Polling in Javascript (fetch, async, await, promise, ....)

Aus TippvomTibb
Zur Navigation springen Zur Suche springen

Allgemeines

Bei einem meiner letzten Proggies (Status-Tablet fuer Smarthome Devices) bin ich mal wieder auf die Suche nach einer Moeglichkeit gegangen, Daten auf einer Webseite zu aktualisieren, sobald sich Daten auf dem Server veraendern, die auf der Browserseite gezeigt werden. Das heiszt

Wie kann ich auf einer bereits im Browser geladenen Seite nachtraeglich Daten aendern und zwar nicht durch Aktion des Nutzers, sondern auf Veranlassung des Servers? 

Vom Grundsatz sind zwei Moeglichkeiten denkbar:

  1. Der Browser fraegt (zeitgesteuert) beim Server nach, ob sich Daten geandert haben. (Poll, to poll englisch fuer umfragen, befragen)
  2. Der Server erhaelt durch eine (weiterhin) geoeffnete Verbindung, die Moeglichkeit Daten an den Browser zu schicken. (Push, to push englisch fuer druecken, schieben)

Die simpelste Variante, dies zu erreichen (<meta http-equiv="refresh" content="5">) hat aber ein paar unschoene Nebeneffekte (Unterbrechnung durch Seitenneuaufbau). Eine weitere Variante habe ich auch schon implementiert: XMLHttpRequest bzw. sein Nachfolger fetch. Damit verschwindet zwar die Neuaufbauunterbrechung, aber diese Methode empfiehlt sich nur wenn das Aktualisierungsintervall deutlich ueber einer Sekunde liegt. Die Serverlast durch dieses Short-Polling kann zum Problem werden. Der naechste Level waere also jetzt das Long-Polling. Bei dem z.B. ueber Javascript ein Request/Anfrage(asynchron) an den Server gesendet wird und die Verbindung (ohne Blockade, parallel, nebenlaeufig) solange gehalten wird bis der Server seine Antwort sendet. Einzig eine Vielzahl von offenen Verbindungen zum Webserver kann bzgl. des Long-Polling hier noch problematisch sein.

Eine weitere Variante, allerdings mit ganz anderem Ansatz baut eine eigene Verbindung ueber einen Websocket zum Server auf. Dies waere bei einem eigenen Client wohl die erste Wahl.

Die Moeglichkeiten ueber SSE oder HTTP/2 habe ich bisher noch nicht verfolgt.

Die Daten koennen auch von einem anderen als dem seitenausliefernden Server stammen, dann spricht man von Cross-Origin Resource Sharing

Derzeit sehe ich keine Notwendigkeit mehr Long-Polling mit Hilfe von XMLHttpRequests (z.B. in FHEM auch XHR genannt) zu realisieren. Die XMLHttpRequest-Technik wurde ursprünglich von Microsoft entwickelt und stand im Internet Explorer ab Version 5.0 (1999!) als ActiveX-Objekt zur Verfügung und ist eigentlich mittlerweile durch die Fetch-API abgeloest. Fuer Browser, die die Fetch-API noch nicht unterstuetzen gibt's Moltofill;-).

Beispiel

<script>
async function fetchData() {
 const response = await fetch('/data.txt');
 // waits until the request completes...
 console.log(response);
}
</script>
Die async function Deklaration definiert eine asynchrone Funktion, die ein AsyncFunction Objekt zurück gibt. Asynchrone Funktionen laufen über den Event Loop auszerhalb des ueblichen Kontrollflusses, und geben als Ergebnis ein implizites Promise Objekt zurück. Die  Syntax und der Aufbau des Codes bei einer asynchronen Funktion aehnelt allerdings der den standardmaessigen synchronen Funktionen.
Ein 'async function' kann auch durch den Ausdruck 'async function expression' definiert werden.

[https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Statements/async_function Quelle mit einem ausfuehrlichem Beispiel.


"async and await make promises easier to write"
async makes a function return a Promise
await makes a function wait for a Promise


[1]


Promise-Objekt

[2] Promise-Objekt Wenn man sich der fetch-Funktion naehert wird es zwangslaeufig notwendig sich ueber ihren Rueckgabewert im Klaren zu werden.

Die fetsch-Funktion hat als Rueckgabewert ein Promise-Objekt.

Das Promise-Objekt, in anderen Programmiersprachen auch Future-Objekt genannt ist eigentlich recht simpel. Das Promise-Objekt besitzt neben dem eigentlichen Inhalt einer Variablen (nur) noch eine weitere Eigenschaft, den Status (state) des Objektes und damit der Variablen. Einfach ausgedrueckt kombiniert man zu einer Variablen noch einen Status, um zu erkennen, ob der Inhalt der Variablen gueltig ist. Eine Variable erhaelt somit einen Status.

A JavaScript Promise object can be:
   Pending
   Fulfilled (besser settled.fulfilled)
   Rejected (besser settled.rejected)

Eigentlich gibt es nur zwei Zusataende: pending und setteld, wobei in der state-Eigenschaft im settled-Zustand vermerkt wird ueber welchen Uebergang dorthin gelangt wurde: fulfilled (erfolgreich) oder rejected (Fehlerfall).

The Promise object supports two properties: state and result.
While a Promise object is "pending" (working), the result is undefined.
When a Promise object is "fulfilled", the result is a value.
When a Promise object is "rejected", the result is an error object.

Da man nicht direkt auf die Objekt-Eigenschaften (privat Properties) zugreifen kann/darf, benoetigt man dafuer (eingebaute) Methoden.

[3]

Konstruktor

var promise = new Promise(executor);

Eigentlich executor function mit den zwei Parametern. Die Parameter sind wiederum Funktionen, oder besser Funktionsnamen. Die Funktionsnamen sind die Namen der sog. Callback-Funktionen, die an die Methoden resolve und reject gebunden sind.

Methoden

  • all
  • allSettled
  • any
  • race
  • reject
  • resolve

Methoden von Promise.prototype

  • then(onFulfill, onReject)
  • catch(onReject)
  • finally(onFinally)
Nebenbemerkung: Eine Funktion ist eine Gruppe von Anweisungen, die durch einen Namen aufgerufen werden. Beim Aufruf der Funktion können Daten übergeben werden, um sie innerhalb der Funktion zu bearbeiten. Funktionen können Daten zurückgeben (den return-Wert).
Eine Methode wird über einen Namen aufgerufen, der zu einem Objekt gehört. Ansonsten sind Methoden und Funktionen bis auf zwei Unterschiede identisch:
   Einer Methode wird indirekt das Objekt übergeben, auf dem sie aufgerufen wird.
   Eine Methode kann auf Daten operieren, die zum Objekt gehören.

https://www.mediaevent.de/javascript/Javascript-Objekte-3.html

Before the adoption of promises, libraries like async.js were used to work with asynchronous code. Now, the native Promise object can be used without having to rely on third-party implementations. With this, we can avoid installing third-party promises or relying on callbacks, and running into callback hell.

Callback-Hell find ich gut. Viele Tutorials leiden unter Notations-Exzessen. Ich bin der Meinung, dass gerade bei der Erlaeuterung eines Sachverhaltes (hier das Promise-Objekt) nur Beispielcode genutzt werden sollte, der frei von Notationsorgien ist. Viele Beispiele zeigen, 'das geht noch' und 'das geht auch noch' und 'so kann man es auch schreiben' und schnell ist der Code fast unlesbar und das Beispiel verfehlt seinen eigentlichen Zweck.

Ein weiteres Negativbeispiel liefert kein geringer als mozilla.org:

Promisesstates.png

Im Zustandsdiagramm sind hier mal locker-flockig Zustaende (states) und Uebergangsbedingungen (Pfeilbeschriftungen) durcheinandergeraten. Hier erhaelt man den Eindruck, dass es noch ein State 'sattled' gibt. Schaut man sich ein Promise-Objekt im Firefox-Inspector an zeigt sich folgendes:

PromiseObjektContent.png

Bleibt nur noch zu klaeren was es mit den Methoden all, allSettled, any und race auf sich hat. [4]

Fetch Function

Einstieg Tiefer Einstieg

Der await Operator wird genutzt, um auf einen Promise zu warten. Er kann nur in einer async Funktion benutzt werden. Der await Ausdruck lässt async Funktionen pausieren, bis ein Promise erfüllt oder abgewiesen ist, und führt die async danach weiter aus. Wenn die Funktion weiter ausgeführt wird, ist der Wert des await Ausdrucks der Wert des erfüllten Promise.

Wenn das Promise abgewiesen wird, wirft der await Ausdruck eine Exception mit dem zurückgewiesenen Wert.

Ist der Wert des Ausdrucks, der auf den await Operator folgt, kein Promise ist, wird dieser zu einem erfüllten Promise (en-US) konvertiert.

Um auf das eingangs gewaehlte Beispiel zureuckzukommen.

<script>
async function fetchData() {
 const response = await fetch('/data.txt');
 // waits until the request completes...
 console.log(response);
}
</script>

Links

[5]