JavaScript JS: Lesen eines C# IAsyncEnuberable funktioniert nicht

Kokujou

Lieutenant
Registriert
Dez. 2017
Beiträge
929
Heyho. Das ist der Stand:
Ich habe ein C# Backend von dem ich ein IAsyncEnumerable zurückgebe. Das funktioniert auch, ich habe es gedebugged und die yield return statements werden ausgelöst.
Code:
C#:
    [HttpPost("import")]
    public async IAsyncEnumerable<MediaModel> ImportRootFolderAsync([FromBody] string rootFolderPath)
    {
        await foreach (var media in _mediaImportService.ImportRootFolderAsync(rootFolderPath)) yield return media;
    }

jetzt versuche ich den Code in JavaScript zu lesen und das interessante ist: Er liest nur das erste Item und wartet dann auf den gesamten Rest der Response. Keine Exception, kein weiterer Iterationsschritt, absolut gar nichts beim debuggen:


Javascript:
        var decoder = new TextDecoder('utf-8');
        for await (const item of /** @type {any} */ (response.body)) {
            var text = decoder.decode(item);
            if (text.startsWith('[')) text = text.substring(1);
            var result = JSON.parse(text);
            yield result;
        }

für andere scheint's zu funktionieren weil ich diese und andere Lösungen schon oft gefunden habe.

Vorher hatte ich auch etwas mit while(true) und body.getReader().read() ausprobiert, was noch weniger funktioniert hat und gleich die ganze Response gelesen hat. irgendwas stimmt da also nicht, irgendwas scheine ich zu vergessen, aber was?

kann mir jemand einen Tipp geben?
 
okay habs raus... es ist ... merkwürdig...
man würde doch meinen, dass die jedes item einzeln zurück kommt oder? also textuell gesehen erst
[{...} dann ,{...} usw... und am Ende ,{...}]

dem ist nicht so... offensichtlich buffered mein server die Antwort. Wenn ich nach jedem yield return ein await HttpContext.Response.FlushAsync() ausführe, geht es zumindest schneller.

allerdings verhindert selbst das ironischerweise nicht dass mal zwei items auf einmal zurück kommen...

also ist die aktuelle behelfslösung, dass ich die items die dann im JSON Format zurück kommen in [] wrappe damit sie als array interpretiert werden und dann durch enumeriere und die einzelnen items yielde.

ich hab auch versucht das IHttpResponseContentFeature auf DisableBuffering zu konfigurieren, aber trotzdem kommen zwischenzeitlich mal zwei items auf einmal zurück... eigenartig.

meine aktuelle Vermutung ist dass die items einzeln zurück kommen, aber der code den ich habe nicht schnell darauf reagiert sodass sie beim zeitpunkt dass der body weitergelesen wird schon zwei items enthält...

hat jemand technische Infos darüber?
 
ich hab auch versucht das IHttpResponseContentFeature auf DisableBuffering zu konfigurieren, aber trotzdem kommen zwischenzeitlich mal zwei items auf einmal zurück... eigenartig.

Ich finde das nicht eigenartig. Das sind, meiner Meinung nach, komplett unabhängige Prozesse.
  1. Senden der Daten
  2. Empfangen der Daten
  3. Verarbeiten der Daten
Du sendest die Daten im Backend. Der Browser empfängt die Daten im Hintergrund und dann verarbeitest du die Daten. Währenddessen du für Daten verarbeitest, sendet dein Backend bereits wieder was und dein Browser empfängt. Das überholt sich dann gegenseitig und dann stehen zwei Antworten im Client bereit. Du hast halt die Annahme getroffen, dass im Client alles einzeln ankommt. Diese Annahme ist nicht richtig und jetzt muss man darauf reagieren.

Im Endeffekt ist deine Vermutung wahrscheinlich richtig. Würde dein Code nicht lokal laufen, hättest du noch die Latenz des Internets dabei und das Phänomen wäre nur selten aufgetreten und hätte dich genervt.
 
  • Gefällt mir
Reaktionen: Kokujou
verstehe, ich hatte beim Implementieren das Observable Pattern im Kopf. Bei WebSockets ist es ja auch so dass nachrichten einzeln verarbeitet werden obwohl sie gleichzeitig ankommen, aber offenbar ist das hier was ganz anderes.
 
Dein body ist einfach ein stream (sofern noch nicht eingelesen und geparst), der nacheinander Bytes ausspuckt. An der Stelle ist gar nichts mehr über dein IAsyncEnumerable bekannt, der Browser weiß davon nichts. Hier ist kein Iterator-Protokoll oder sonstiges. Wie du mit diesen hereinströmenden Bytes jetzt umgehst ist deine Sache.

Aktuell schickst du die in den TextDecoder. Dass der die Items immer in gänze ausspuckt, ist einfach nur Zufall, weil die Daten halt so komplett in einem IP-Paket(-Schwung) ankommen. An sich wäre es aber auch möglich, dass der mal nur ein halbes JSON-Objekt ausspuckt (z.B. wenn Objekt groß und Verbindung langsam), dann würdest du beim JSON.parse einen Error bekommen.

Was du suchst ist ein JSON-Parser, der gestreamte Bruchstücke annehmen kann und dir dann sobald ein Stück voll da ist, dir das ausspuckt. Eine naive Implementierung ließt z.B. einfach solange ein, bis }, kommt und spuckt das dann aus.
 
naja hoffen wir einfach mal dass zumindest ein ganzes response objekt kommt.
weil sowas selbst schreiben ist ziemlich komplex, ich meine ich kriege ja nicht byte für byte zurück, was ich kriege ist einfach eine zufälllige anzahl von bytes so wie du das sagst. ich müsste das also quasi runter auf die byte-ebene brechen und solange lesen bis json was davon versteht. Aber Achtung... so einfach isses ja auch nicht, weil was ich kriege ist ein Array von Objekten... Also müsste ich erstmal das erste Zeichen also [ ignorieren, und dann solange weiterlesen bis ein valides JSON Objekt bei rauskommt, um dann wieder ein zeichen zu ignorieren (diesmal ",")

ich werd's mal beobachten, wenn für meinen use case jemals ein halbes JSON Objekt kommt muss ich wohl das Protokoll wechseln, dann muss ich etwas nehmen das wie websockets funktioniert. bei websockets kommt immer eine ganze nachricht an. nicht zwei nachrtichten auf einmal, keine halbe nachricht, exakt eine nachricht. damit könnte mal das yield return prinzip vermutlich gut umsetzen.
Ergänzung ()

was ich interessant finde ist dass sämtliche code beispiele und chatGPT mir alle gesagt haben "ne, das passt schon so" und keiner hatte diese Probleme oder überhaupt eine Ahnung was bei mir verquer läuft :D

aber glücklicherwiese habt ihr mich in die richitge Richtung gestoßen :)
 
Zurück
Oben