Forum: PC-Programmierung Webserial Chrome


von Christoph M. (mchris)


Angehängte Dateien:

Lesenswert?

Mittlerweile sollte es theoretisch einfach sein, über eine Webbrowser 
direkt auf die serielle Schnittstelle zuzugreifen und z.B. Daten zum 
Mikrocontroller zu übertragen:

https://tigoe.github.io/html-for-conndev/webSerial/

Nicht jeder Browser ist kompatibel:

https://entwickler.de/api/webserial-api-webapplikation-hardware-interaktion

Bei mir funktioniert das leider nicht. Webserial scheint von Chrome 
unterstützt, aber er zeigt keine Schnittstellen an (Bild). Woran könnte 
es liegen?

System: Ubuntu 20.04
Chromiung: Version 114.0.5735.198 (Offizieller Build) snap (64-Bit)

christoph@..:~$ groups
christoph .. dialout .. plugdev .. usbusers

von Εrnst B. (ernst)


Angehängte Dateien:

Lesenswert?

Christoph M. schrieb:
> Chromiung: Version 114.0.5735.198 (Offizieller Build) snap (64-Bit)

Würde vermuten dass "snap" daran schuld ist. Da wird der Chromium 
nochmal in sein eigenes "Gefängnis" gesperrt, und wenn die Snap-Macher 
nicht daran gedacht haben, auch serielle Ports dort rein zu lassen, wird 
das nix.

Screenshot:
Version 114.0.5735.106 (Offizieller Build) built on Debian 12.0, running 
on Debian 12.0 (64-Bit)


-> Installier dir einen Chrome, der kommt ohne Snap. Oder such eine 
Anleitung, wie du die Debian-Chromium-Paketquellen in's Ubuntu 
gefrickelt kriegst.


Edit:
Evtl reicht auch ein
>> sudo snap connect chromium:raw-usb

um das Chromium-Snap an USB-Devices ranzulassen.

: Bearbeitet durch User
von Christoph M. (mchris)


Lesenswert?

Danke für die Hilfe :-)

>Evtl reicht auch ein
>>> sudo snap connect chromium:raw-usb

>um das Chromium-Snap an USB-Devices ranzulassen.

Das habe ich jetzt probiert, aber es scheint nicht zu funktionieren.

Ich habe das Snap-Paket mal deinstalliert und dann mit

https://askubuntu.com/questions/510056/how-to-install-google-chrome

> ..
> sudo apt-get update
> sudo apt-get install google-chrome-stable

Chrome direkt installiert. Zeigt aber weiterhin keine Schnittstellen.

Funktioniert Webserial bei Dir? Wenn ja, auf welchem System?

von Εrnst B. (ernst)


Lesenswert?

Christoph M. schrieb:
> Funktioniert Webserial bei Dir? Wenn ja, auf welchem System?

Ja. Debian 12, sowohl mit Chromium als auch mit Chrome.

Deine Installations-Anleitung ist von 2014, auch wenn die mal 
aktualisiert wurde.

Keine Ahnung warum das über ein PPA läuft. Du kannst Chrome auch direkt 
von hier herunterladen: https://www.google.com/chrome/

Und dann einfach das heruntergeladene .deb installieren. Das legt dann 
auch einen sources.list Eintrag für automatische Updates an.

Kann aber sein, dass das PPA exakt dasselbe tut.

Chromium kann problemlos parallel installiert bleiben.

von Εrnst B. (ernst)


Angehängte Dateien:

Lesenswert?

Screenshot vergessen:
Serial-Terminal über Chromium zu einem ESP8266-Patinchen.

https://googlechromelabs.github.io/serial-terminal/

von Le X. (lex_91)


Lesenswert?

Kannst du denn aus dem Terminal auf das serielle Gerät zugreifen?
Irgendwann musste man unter Debian oder Ubuntu den eigenen User in die 
dialout-Gruppe stecken.

von Εrnst B. (ernst)


Lesenswert?

Le X. schrieb:
> Kannst du denn aus dem Terminal auf das serielle Gerät zugreifen?
> Irgendwann musste man unter Debian oder Ubuntu den eigenen User in die
> dialout-Gruppe stecken.

hat er im ersten Post geschrieben, er ist in dialout und plugdev, das 
sollte schon reichen.

Beitrag #7451446 wurde vom Autor gelöscht.
von Christoph M. (mchris)


Lesenswert?

>Keine Ahnung warum das über ein PPA läuft. Du kannst Chrome auch direkt
>von hier herunterladen: https://www.google.com/chrome/

Ok, vielen Dank für die Hilfe. Jetzt läuft es.

Mich würde interessieren, wie es bei den Windows-Usern so ist: Läuft es 
dort ebenfalls? Ziel: eine platforunabhängige GUI für Mikrocontroller. 
Deshalb wäre es gut, wenn Webserial auf allen Platformen einigermaßen 
problemlos läuft.

Was mir auffällt: Das Beispiel vom Eingangspost läuft nicht so gut. Die 
empfangenen Zeichen sind nicht sonderlich gut synchronisiert.

https://tigoe.github.io/html-for-conndev/webSerial/

Das Terminal

https://googlechromelabs.github.io/serial-terminal/

läuft, aber wenn ich senden will scheint es Probleme mit CR/LF zu geben. 
Da wäre die Frage, ob das bei Windows per default anders ist.

von Εrnst B. (ernst)


Lesenswert?

Christoph M. schrieb:
> läuft, aber wenn ich senden will scheint es Probleme mit CR/LF zu geben.

Ändert der "Convert EOL"-Knopf das?

Aber eigentlich egal, wenn du deine eigene Oberfläche für deine eigenen 
µC-Projekte baust, kannst du das Zeilenende-Zeichen auf beiden Seiten 
passend festlegen.
Auch das Buffering für empfangene Zeichen kannst du da so handhaben, wie 
es für deine Anwendung passt.

von Christoph M. (mchris)


Angehängte Dateien:

Lesenswert?

Im Moment versuche ich, mit Webserial Daten zu senden und zu empfangen.
Dazu ein kurzes Beispiel: Drückt man den "send" Knopf, wird das Wort 
"hello" gesendet. Alle Zeichen von der seriellen Schnittstelle sollen im 
Browserfenster angezeigt werden.

Aus mir unerfindlichen Gründen wird nur das "hello" gesendet. Man sieht 
es auf dem Oszilloskop. Das Empfangen will aber einfach nicht 
funktionieren.

Hat jemand eine Idee?

Webserial Code:
1
<!DOCTYPE html>
2
<html>
3
<head>
4
    <title>Web Serial Example</title>
5
</head>
6
<body>
7
    <button id="connectButton">Connect to Serial Device</button>
8
    <button id="sendButton" disabled>Send "Hello"</button>
9
    <div id="receivedData" style="border: 1px solid #ccc; padding: 10px; margin-top: 10px;"></div>
10
11
    <script>
12
        const connectButton = document.getElementById('connectButton');
13
        const sendButton = document.getElementById('sendButton');
14
        const receivedDataDiv = document.getElementById('receivedData');
15
        let port;
16
17
        connectButton.addEventListener('click', async () => {
18
            try {
19
                port = await navigator.serial.requestPort();
20
                await port.open({ baudRate: 115200 }); // Set baud rate to 115200
21
                sendButton.disabled = false;
22
                connectButton.disabled = true;
23
24
                port.addEventListener('readable', onSerialDataReceived);
25
            } catch (error) {
26
                console.error('Error opening serial port:', error);
27
            }
28
        });
29
30
        sendButton.addEventListener('click', async () => {
31
            if (!port) return;
32
            try {
33
                const writer = port.writable.getWriter();
34
                const message = 'Hello\n';
35
                writer.write(new TextEncoder().encode(message));
36
                writer.releaseLock();
37
            } catch (error) {
38
                console.error('Error sending data:', error);
39
            }
40
        });
41
42
        async function onSerialDataReceived() {
43
            const reader = port.readable.getReader();
44
            try {
45
                while (true) {
46
                    const { value, done } = await reader.read();
47
                    if (done) {
48
                        break;
49
                    }
50
                    const receivedText = new TextDecoder().decode(value);
51
                    receivedDataDiv.innerHTML += receivedText + '<br>';
52
                }
53
            } catch (error) {
54
                console.error('Error reading data:', error);
55
            } finally {
56
                reader.releaseLock();
57
            }
58
        }
59
    </script>
60
</body>
61
</html>

Das Echo-Programm auf dem Mikrocontroller sieht so aus:
1
int incomingByte = 0;    // for incoming serial data
2
void setup()
3
{
4
  Serial.begin(115200);    // opens serial port, sets data rate to 9600 bps
5
}
6
7
void loop()
8
{
9
  if (Serial.available() > 0)
10
  {
11
    incomingByte = Serial.read();
12
    Serial.println((char)incomingByte);
13
  }
14
}

von Εrnst B. (ernst)


Angehängte Dateien:

Lesenswert?

Christoph M. schrieb:
> port.addEventListener('readable', onSerialDataReceived);

gibt es nicht. SerialPort hat events für connect/disconnect, aber nicht 
für "readable".

Brauchst du auch nicht, du kannst einfach "async" vom reader lesen.

d.H.:
Ändere Zeile

port.readable.addEventListener('readable', onSerialDataReceived);
in
onSerialDataReceived();
(ohne "await"!)

Und schon funktioniert dein Beispielcode.
(Vorschlag auf kleinste Änderung hin ausgewählt, nicht auf schönsten 
Code)




Nachtrag: Falls du dich wunderst, warum ich "Hello" im receivedData 
stehen habe und kein

H
e
l
l
o


Ich hab keinen Arduino mit deinem Code angeklemmt, sondern einfach einen 
USB->Serial Stöpsel mit Jumper über Rx/Tx.

: Bearbeitet durch User
von Christoph M. (mchris)


Lesenswert?

Vielen Dank, funktioniert super.

>Ich hab keinen Arduino mit deinem Code angeklemmt, sondern einfach einen
>USB->Serial Stöpsel mit Jumper über Rx/Tx.

Gute Idee. Das Arduino-Script habe ich für's debugging gemacht, weil ich 
vermutete, dass Webserial vielleicht für den Empfang CR und NL braucht.
War wohl nicht so ;-)

Hier mal eine Version, die einen Zähler sendet und die empfangenen Werte 
in einem Scrollfenster darstellt. Wichtig ist, dass erst beim Empfang 
des NL upgedatet wird, sonst hat man immer Unterbrechungen in der 
Ausgabe.
1
<!DOCTYPE html>
2
<html>
3
<head>
4
    <title>Web Serial Example</title>
5
</head>
6
<body>
7
    <button id="connectButton">Connect to Serial Device</button>
8
    <button id="sendButton" disabled>Send "Counter"</button>
9
    <div id="receivedData" style="border: 1px solid #ccc; padding: 10px; margin-top: 10px; height: 200px; overflow: auto;"></div>
10
11
    <script>
12
        const connectButton = document.getElementById('connectButton');
13
        const sendButton = document.getElementById('sendButton');
14
        const receivedDataDiv = document.getElementById('receivedData');
15
        let port;
16
        let reader;
17
        let receivedBuffer = '';
18
        let counter = 0;
19
20
        connectButton.addEventListener('click', async () => {
21
            try {
22
                port = await navigator.serial.requestPort();
23
                await port.open({ baudRate: 115200 }); // Set baud rate to 115200
24
                sendButton.disabled = false;
25
                connectButton.disabled = true;
26
27
                reader = port.readable.getReader();
28
                startReadingSerialData();
29
            } catch (error) {
30
                console.error('Error opening serial port:', error);
31
            }
32
        });
33
34
        sendButton.addEventListener('click', async () => {
35
            if (!port) return;
36
            try {
37
                const writer = port.writable.getWriter();
38
                const message = `Counter: ${counter}\n`; // Append '\n' for newline
39
                writer.write(new TextEncoder().encode(message));
40
                writer.releaseLock();
41
                counter++;
42
            } catch (error) {
43
                console.error('Error sending data:', error);
44
            }
45
        });
46
47
        async function readSerialData() {
48
            try {
49
                while (true) {
50
                    const { value, done } = await reader.read();
51
                    if (done) {
52
                        break;
53
                    }
54
                    const receivedText = new TextDecoder().decode(value);
55
                    receivedBuffer += receivedText;
56
                    while (receivedBuffer.includes('\n')) {
57
                        const newlineIndex = receivedBuffer.indexOf('\n');
58
                        const line = receivedBuffer.substring(0, newlineIndex + 1);
59
                        receivedBuffer = receivedBuffer.substring(newlineIndex + 1);
60
                        appendToReceivedData(line);
61
                    }
62
                }
63
            } catch (error) {
64
                console.error('Error reading data:', error);
65
            }
66
        }
67
68
        function appendToReceivedData(text) {
69
            receivedDataDiv.innerHTML += text + '<br>';
70
            receivedDataDiv.scrollTop = receivedDataDiv.scrollHeight;
71
        }
72
73
        async function startReadingSerialData() {
74
            readSerialData();
75
        }
76
77
        async function stopReadingSerialData() {
78
            if (reader) {
79
                await reader.cancel();
80
                reader = null;
81
            }
82
        }
83
84
        connectButton.addEventListener('click', startReadingSerialData);
85
        window.addEventListener('beforeunload', stopReadingSerialData);
86
    </script>
87
</body>
88
</html>

Was mir jetzt noch fehlt: Es sollen die empfangenen Werte in 
verschiedene Textausgaben umgeleitet werden.

Empfang     Textausgabe
========   ============
zaehlerA   -> textfeld1
zaehlerA   -> textfeld2
zaehlerA   -> textfeld3

von Εrnst B. (ernst)


Lesenswert?

Christoph M. schrieb:
> const receivedText = new TextDecoder().decode(value);
> receivedBuffer += receivedText;

Kleine Anmerkung, das kann später schwer zu findende "Heisenbugs" geben:
der Standard-Textdecoder ist für UTF-8, und da kann ein Zeichen mehrere 
Bytes lang sein.
Der gelesene Chunk von der Seriellen kann mitten in einem UTF-8-Zeichen 
enden, das Zeichen ginge dann verloren.
Der TextDecoder kann das handhaben, aber natürlich nur, wenn du nicht 
ständig neue Decoder erstellst, sondern denselben TextDecoder für den 
gesamten Datenstrom beibehältst. (Dann ".decode(value, { stream: !done 
});" mit dem "done"-Boolean aus dem reader.read() )

Oder: TextDecoder mit Ascii initialisieren, da ist "ein Byte"=="ein 
Buchstabe".

Oder: Gleich einen "TextDecoderStream" nehmen, mit ".pipeThrough()" 
direkt an den Serial-reader-Stream koppeln.

Übrigens, wenn du sowieso schon am "Streamen" der Daten bist:
1
// untested
2
const lineTransformer = new TransformStream({
3
  start(controller) {
4
    this.partialLine = '';
5
  },
6
  transform(chunk, controller) {
7
    const str = this.partialLine + chunk.toString();
8
    const lines = str.split('\n');
9
    for (let i = 0; i < lines.length - 1; i++) {
10
      controller.enqueue(lines[i]);
11
    }
12
    this.partialLine = lines[lines.length - 1];
13
  },
14
  flush(controller) {
15
    if (this.partialLine !== '') {
16
      controller.enqueue(this.partialLine);
17
    }
18
  }
19
});
20
21
const readLineStream = port.readable.pipeThrough(new TextDecoderStream(new TextDecoder())).pipeThrough(lineTransformer);
Und schon kannst du aus dem readLineStream.getReader() immer schön ganze 
Zeilen lesen...

: Bearbeitet durch User
von Christoph M. (mchris)


Lesenswert?

>Kleine Anmerkung, das kann später schwer zu findende "Heisenbugs" geben:
>der Standard-Textdecoder ist für UTF-8, und da kann ein Zeichen mehrere
>Bytes lang sein.

Danke für den Hinweis. Sollte der Mikrocontroller nicht immer ganze 
Bytes senden?
1
const readLineStream = port.readable.pipeThrough(new TextDecoderStream(new TextDecoder())).pipeThrough(lineTransformer)

Das verstehe ich nicht ganz. Wie kann ich den LineTransformer dann 
lesen?

P.s: kleiner Seitenbranch ..

Ich habe gerade mal die Pipico "Scopy" App auf dem Handy installiert

Beitrag "PiPico Oszilloskop"

den Pipico mit der Scopy Firmware geflasht. Es funktioniert und die App 
kann mit dem Pipico kommunizieren.
Dann habe ich
>Serial-Terminal über Chromium zu einem ESP8266-Patinchen.
https://googlechromelabs.github.io/serial-terminal/

ausprobiert, aber behauptet Chrome, das nicht zu unterstützen. Ich habe 
ein älteres S8, vielleicht liegt es daran, oder geht es grundsätzlich 
nicht?

von Εrnst B. (ernst)


Lesenswert?

Christoph M. schrieb:
> Danke für den Hinweis. Sollte der Mikrocontroller nicht immer ganze
> Bytes senden?

Ganze Bytes schon, aber keine ganzen Buchstaben (Du verwendest UTF-8)

ein 'Ä' ist da z.B. 0xC3 0x84

Wenn du dem TextDecoder (wegen blöd gelegenem Chunk) zuerst ein 0xC3 
gibst, und danach einem neuem TextDecoder das 0x84, kann keiner von 
Beiden was damit anfangen.

Deshalb: TextDecoder nicht immer wieder frisch mit "new" anlegen, 
solange leben lassen wie der ReadStream lebt. Und Daten mit 
"stream:true" einfüllen, wenn die Daten gestreamt werden, wie das am 
Seriellen Port nunmal so ist. Wenn das zu kompliziert ist: der 
TextDecoderStream kapselt das für dich weg.

Oder zu verzichtest auf UTF-8, und nimmst z.B. Ascii oder ISO-8859-15, 
dann musst du das aber dem TextDecoder im Konstruktor mitteilen.

Christoph M. schrieb:
> Das verstehe ich nicht ganz. Wie kann ich den LineTransformer dann
> lesen?

wie bisher auch, nur dass eben immer ein einzeiliger String rausfällt.
1
  const reader=readLineStream.getReader();
2
  while (true) {
3
     const { value, done } = await reader.read();
4
     // value ist jetzt ein string, und immer eine Zeile.
5
...

Oder du packst dir einen WritableStream hinter den lineTransformer, dann 
kannst du auch ohne "while(true)-Schleife immer dann einen Callback 
bekommen, wenn wieder eine Zeile komplett gelesen wurde.



...

Android: Nein.
https://caniuse.com/web-serial
bzw. Jain: Über das Polyfill, was per Web-USB direkt auf USB->Seriell 
Adapter zugreift, und deren "Treiber" in Javascript nachgebaut hat, geht 
es evtl.
https://caniuse.com/webusb

von Εrnst B. (ernst)


Angehängte Dateien:

Lesenswert?

Ich hab das mal in dein Beispielprogramm zusammengepackt.

im Endeffekt:
1
                const lineReceiver = new WritableStream({
2
                  write: lineReceived
3
                });
4
                port.readable
5
                  .pipeThrough(new TextDecoderStream())
6
                  .pipeThrough(lineTransformer)
7
                  .pipeTo(lineReceiver);
Bewirkt dass "lineReceived" als Callback automatisch ausgeführt wird, 
sobald eine Zeile komplett ist.

Beim Senden (am Hallo-Knopf) habe ich eingebaut, dass er einen Teil 
schön langsam Byteweise schickt, und einen Block mit mehreren Zeilen in 
einem Rutsch, um zu Zeigen dass so der Text-Decoder trotz Sonderzeichen 
nicht aus dem Tritt kommt, und der lineTransformer auch bei mehreren 
Zeilen in einem Chunk alle Zeilen brav an den callback übergibt.

Zum Testen hatte ich wieder Rx/Tx gebrückt.

von Christoph M. (mchris)


Angehängte Dateien:

Lesenswert?

Vielen Dank, das scheint gut zu funktionieren.

Zuerst war ich durch die Delays irritiert. Aber wenn man sie raus macht, 
funktioniert es gut.
Ich habe das Beispiel mal so verändert, dass beim Drücken des Tasters 
eine Zähler erhöht, gesendet und der Empfangsstring dann dargestellt 
wird.

von Εrnst B. (ernst)


Lesenswert?

Christoph M. schrieb:
> Zuerst war ich durch die Delays irritiert. Aber wenn man sie raus macht,
> funktioniert es gut.

Wie geschrieben, die waren zum Testen drinnen, um eine maximal 
ungünstige Unterteilung des Datenstroms in gelesene Chunks zu erreichen.
Wenn der Test mit so einem Worst-Case funktioniert, wird's auch in der 
Realität bei wesentlich "angenehmeren" Datenblöcken funktionieren.

von Harald K. (kirnbichler)


Lesenswert?

Εrnst B. schrieb:
> TextDecoder mit Ascii initialisieren

Statt ASCII sollte man ISO8859-1 bzw. CP1252 (auch "Windows ANSI" 
genannt) verwenden, dann lassen sich auch die üblichen Sonderzeichen, 
die im mitteleuropäischen Raum üblich sind, verwenden. Jedes 
darstellbare Zeichen ist dann genau ein Byte groß.

Allerdings sollte man auch den Quelltext für den µC in dieser Codierung 
erstellen.

So schick UTF-8 an mancher Stelle auch sein mag, so extrem ungeeignet 
ist es bei µC-Anwendungen.

Terminalprogramme wie z.B. Teraterm verwenden ebenfalls diese 
Zeichencodierung.

von Εrnst B. (ernst)


Lesenswert?

Harald K. schrieb:
> Statt ASCII sollte man ISO8859-1 bzw. CP1252 (auch "Windows ANSI"
> genannt) verwenden, dann lassen sich auch die üblichen Sonderzeichen,
> die im mitteleuropäischen Raum üblich sind, verwenden. Jedes
> darstellbare Zeichen ist dann genau ein Byte groß.

ISO8859-15 hatte ich vorgeschlagen, €.
Aber es tut ja nicht weh den TextDecoder einfach richtig zu verwenden, 
dann funktioniert es mit jeder Kodierung, egal ob 7-Bit-ASCII oder 
UTF-32.
Und man muss sich (außer dem Einstellen der verwendeten Kodierung im 
Konstruktor) nie wieder Gedanken darum machen.

von Harald K. (kirnbichler)


Lesenswert?

Εrnst B. schrieb:
> Aber es tut ja nicht weh den TextDecoder einfach richtig zu verwenden,
> dann funktioniert es mit jeder Kodierung, egal ob 7-Bit-ASCII oder
> UTF-32.

Dann muss aber die Gegenstelle (oft ein µC) auch davon wissen.

strlen und UTF-8 kann lustig werden.

von Εrnst B. (ernst)


Lesenswert?

Harald K. schrieb:
> Dann muss aber die Gegenstelle (oft ein µC) auch davon wissen.

Na und? Wenn der µC es nicht kann oder nicht können soll, nimmt man eben 
einen 8-Bit-Zeichensatz.

Ist trotzdem kein Grund, auf PC-Seite das Zeichensatz-Dekodieren 
absichtlich falsch zu implementieren, wenn der korrekte Code sogar noch 
kürzer, einfacher und resourcenschonender ist.

von Christian M. (christian_m280)


Lesenswert?

Christoph M. schrieb:
> Ziel: eine platforunabhängige GUI für Mikrocontroller. Deshalb wäre es
> gut, wenn Webserial auf allen Platformen einigermaßen problemlos läuft.

Mich würde brennend interessieren ob das jemand bestätigen kann! Oder 
halt auf den meisten Plattformen halt...

Gruss Chregu

von Εrnst B. (ernst)


Angehängte Dateien:

Lesenswert?

Christian M. schrieb:
> Mich würde brennend interessieren ob das jemand bestätigen kann! Oder
> halt auf den meisten Plattformen halt...

sh. oben.

Εrnst B. schrieb:
> Android: Nein.
> https://caniuse.com/web-serial
> bzw. Jain: Über das Polyfill, was per Web-USB direkt auf USB->Seriell
> Adapter zugreift, und deren "Treiber" in Javascript nachgebaut hat, geht
> es evtl.
> https://caniuse.com/webusb

Also:
Android bei manchen USB->Seriell Wandlern per Polyfill,
iPhone schaut in die Röhre,
Windows und Linux: Problemlos, zumindest solange man nicht auf Windows95 
o.Ä. festhängt und deshalb keine Browser-Updates mehr kriegt.
Desktop-Mac: Hab ich nicht getestet, vermutlich nur mit Chrome, Safari 
bleibt außen vor.

Mit den Daten von caniuse: 30% aller User deckst du damit ab, am Desktop 
ca. 75%, wenn die Leute bereit sind einen vmtl. schon installierten, 
aber selten verwendeten Browser zu Starten: Schätze 80-90%.

Inzwischen stammt eben ein Großteil des Web-Traffics von 
Telefonen/Tablets, deshalb der große Unterschied zwischen "alle User" 
und "Desktop only"

: Bearbeitet durch User
von Daniel A. (daniel-a)


Lesenswert?

Εrnst B. schrieb:
> wie bisher auch, nur dass eben immer ein einzeiliger String rausfällt.
> const reader=readLineStream.getReader();
>   while (true) {
>      const { value, done } = await reader.read();
>      // value ist jetzt ein string, und immer eine Zeile.

WTF, die ReadableStream API ist praktisch ein AsyncIterator, aber mit 
minimalen inkompatiblen unterschieden!?!

Der reader von einem ReadableStream hat die Methoden read(), cancel(), 
releaseLock(). Ein AsyncGenerator hat next(), return(), throw().
read() und next() geben beide eine Promise zurück, beide resultieren in 
einem Objekt mit {value, done}.

Und sie implementieren noch nicht einmal Symbol.asyncIterator! Würden 
sie das machen, gienge ein simples "for await"

Naja, muss man halt manuell konvertieren:
1
async function* readerToAsyncIterator(r){
2
  r ??= this;
3
  while(true){
4
    const res = await r.read();
5
    if(res.done) return;
6
    try {
7
      yield res.value;
8
    } catch(e) {
9
      r.cancel(e);
10
      throw e;
11
    }
12
  }
13
}

Dann sollte das hier gehen (ungetestet):
1
const reader = readLineStream.getReader();
2
for await(const value of readerToAsyncIterator(reader)){
3
  
4
}

Mit etwas monkypatching geht es noch einfacher, ist aber ein wenig ein 
hack:
1
ReadableStreamDefaultReader.prototype[Symbol.asyncIterator] = readerToAsyncIterator;
2
const reader = readLineStream.getReader();
3
for await(const value of reader){
4
  
5
}

von Εrnst B. (ernst)


Lesenswert?

Daniel A. schrieb:
> WTF, die ReadableStream API ist praktisch ein AsyncIterator, aber mit
> minimalen inkompatiblen unterschieden!?!

Um das WTF zu verstärken:
Am Firefox(>v110) ist der ReadableStream async iterable, am Chrome 
nicht.
Firefox unterstützt kein Webserial, Chrome schon.

https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream#browser_compatibility

von Christoph M. (mchris)


Lesenswert?

Daniel A. (daniel-a)
>WTF, die ReadableStream API ist praktisch ein AsyncIterator, aber mit
>minimalen inkompatiblen unterschieden!?!

Schön wäre es, wenn du aus deinem Beitrag ein ausführbares Beispiel 
machst, damit die Leute schnell ausprobieren können, ob es auch wirklich 
funktioniert und besser ist, als die von Ernst vorgeschlagene Methode.

Hier ein Beispiel, wie das aussehen kann:

Beitrag "Re: Webserial Chrome"

von Εrnst B. (ernst)


Lesenswert?

Christoph M. schrieb:
> ob es auch wirklich
> funktioniert und besser ist, als die von Ernst vorgeschlagene Methode.

Er hat ja nicht wirklich was daran geändert, nur den Stream-Reader von 
Chrome so umgepatcht (auf den Stand vom Firefox gebracht...), dass man 
statt dem umständlichen
1
  while (true) {
2
     const { value, done } = await reader.read();

das schönere
1
for await (const value of reader){

schreiben kann.

Wenn man die Variante mit dem "lineReceived"-Callback verwenden mag 
ändert sich nix, da entfällt die Schleife komplett.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.