Michael_K: Passing a Blob

Hallo,

Ich sende einen Blob zwischen zwei iframes (via eventListeners). Das funktioniert auch so weit ganz gut. Allerdings verliert das Blob-Object einen key, den ich vorher dem Blob noch hinzugefügt habe. Ist dieses Verhalten irgendwo beschrieben, warum und wie die Eigenschaften gelöscht werden?

Also in etwa so:

const aBlob = getBlobFromAFunction();
aBlob.name =  'a name key with string value is added to the blob ';

Wenn ich nun den Blob and den iFrame sende, dann kommt dieser auch an, aber ohne den key "name". Ich möchte verstehen warum und wo man es nachlesen kann.

Gibt es dafür eine Quelle?

Gruss, Michael

  1. Hi,

    const aBlob = getBlobFromAFunction();
    aBlob.name =  'a name key with string value is added to the blob ';
    

    hm. Erst wird das Ding als const, also unveränderlich bezeichnet, und im nächsten Moment verändert …

    Wenn ich nun den Blob and den iFrame sende, dann kommt dieser auch an, aber ohne den key "name".

    Bitte auf keinen Fall verraten, wie das Senden aussieht.

    cu,
    Andreas a/k/a MudGuard

    1. Tach!

      const aBlob = getBlobFromAFunction();
      aBlob.name =  'a name key with string value is added to the blob ';
      

      hm. Erst wird das Ding als const, also unveränderlich bezeichnet, und im nächsten Moment verändert …

      Nein, ein Ändern würde abgelehnt werden. Aber const bezieht sich nur auf den Wert in der Variable selbst. Bei Objekten ist das lediglich die Referenz und nicht das referenzierte Objekt. const hat nicht die Aufgabe von Object.freeze(). Selbst Object.freeze() geht nicht rekursiv durch Unterobjekte.

      dedlfix.

      1. Source:

        const aBlob = 'bar';
        aBlob.foo =  'tok';
        
        console.log(JSON.stringify(aBlob));
        console.log(JSON.stringify(aBlob.foo));
        
        

        Test:

        /tmp$ node test.js 
        "bar"
        undefined
        

        Ob ich ich aBlob mit const, var oder nicht deklariere ist egal.

        Jetzt bin ich nicht so der JS-Guru. Aber könnte es sein, dass @Michael_K das so ähnlich macht?

        1. Tach!

          Source:

          const aBlob = 'bar';
          

          Ein Blob ist ein Objekt und kein String. Falscher Versuchsaufbau bei dir.

          Jetzt bin ich nicht so der JS-Guru. Aber könnte es sein, dass @Michael_K das so ähnlich macht?

          Es kann alles mögliche sein, was er so macht. Spekulieren lohnt da nicht. Ein nachvollziehbarer Versuchsaufbau wäre sinnvoll.

          dedlfix.

          1. Falscher Versuchsaufbau bei dir.

            Ich hege die Vermutung, dass die Michaels Funktion getBlobFromAFunction(); ebenfalls nur den puren Blob bzw. String liefert.

            Das hier funktioniert jedenfalls wie erwartet:

            const aBlob = {};
            aBlob.value = 'bar';
            aBlob.foo   = 'tok';
            
            console.log(JSON.stringify(aBlob));
            console.log(JSON.stringify(aBlob.foo));
            

            Test:

            node test.js 
            {"value":"bar","foo":"tok"}
            "tok"
            

            Auch:

            const aBlob = getValue();
            aBlob.value =  'bar';
            aBlob.foo =  'tok';
            
            console.log(JSON.stringify(aBlob));
            console.log(JSON.stringify(aBlob.foo));
            
            
            function getValue() {
            		var o={};
            		o.value='bar';
            		return o;
            }
            

            funktioniert.

            const aBlob = getValue();
            aBlob.value =  'bar';
            aBlob.foo =  'tok';
            
            console.log(JSON.stringify(aBlob));
            console.log(JSON.stringify(aBlob.foo));
            
            
            function getValue() {
            		var o='bar';
            		return o;
            }
            

            indes nicht.

            1. Die Zeile

              aBlob.value =  'bar';
              

              hat sich in die funktionierende und nicht funktionierende Variante dreist eingeschmuggelt, kann (und sollte) für eigene Tests also weg…

      2. Hi,

        const aBlob = getBlobFromAFunction();
        aBlob.name =  'a name key with string value is added to the blob ';
        

        Nein, ein Ändern würde abgelehnt werden.

        Ich kann nicht sehen, was bei Michael K in der Console steht …

        cu,
        Andreas a/k/a MudGuard

        1. Ich kann nicht sehen, was bei Michael K in der Console steht …

          Zumindest mit node kommt keinerlei Ablehnung, Warnung oder Notiz. Es wird einfach nicht getan.

          export NODE_NO_WARNINGS=0;
          node test.js
          
          

          hab ich versucht … Soweit dann zum Meckern über andere Programmiersprachen.

          1. Tach!

            Ich kann nicht sehen, was bei Michael K in der Console steht …

            Zumindest mit node kommt keinerlei Ablehnung, Warnung oder Notiz. Es wird einfach nicht getan.

            const foo = {};
            foo = 42;
            

            Das wäre der Fall, dass foo geändert werden soll. Eine solche Zuweisung wird abgelehnt.

            Das Zuweisen von Eigenschaften ist eine andere Geschichte und problemlos möglich, da ja nicht das Objekt konstant ist, sondern nur die Referenz darauf in der Variable foo.

            Dein Test, dem String eine Eigenschaft zuzuweisen,

            const foo = 'bar';
            foo.qux = 42;
            

            wird als Noop klaglos ausgeführt. Es sei denn, der Strict Mode ist aktiviert. Es sei denn, der Prototyp von String hat einen Setter.

            Aber auch das hat nichts damit zu tun, dass foo als const deklariert wurde.

            dedlfix.

            1. wird als Noop klaglos ausgeführt. Es sei denn, der Strict Mode ist aktiviert.

              (Noop bedeutet: Das tut einfach nichts.)

              Da hätten wir also den Lösungsweg und die Ursache:

              'use strict';
              
              const aBlob = getValue();
              aBlob.foo =  'tok';
              
              console.log(JSON.stringify(aBlob));
              console.log(JSON.stringify(aBlob.foo));
              
              function getValue() {
              		return 'bar';
              }
              

              Jetzt mit Fehlermeldungen:

              node test.js 
              /tmp/test.js:4
              aBlob.foo =  'tok';
                        ^
              
              TypeError: Cannot create property 'foo' on string 'bar'
                  at Object.<anonymous> (/tmp/test.js:4:11)
                  at Module._compile (node:internal/modules/cjs/loader:1092:14)
                  at Object.Module._extensions..js (node:internal/modules/cjs/loader:1121:10)
                  at Module.load (node:internal/modules/cjs/loader:972:32)
                  at Function.Module._load (node:internal/modules/cjs/loader:813:14)
                  at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:76:12)
                  at node:internal/main/run_main_module:17:47
              
              
              1. Tach!

                (Noop bedeutet: Das tut einfach nichts.)

                Da hätten wir also den Lösungsweg und die Ursache:

                Nein, haben wir nicht. Du gehst davon aus, dass der Blob ein String sei. Blobs sind üblicherweise Objekte. Der OP schreibt auch, dass er ein Blob-Objekt hat. Was er wirklich vorliegen hat und wie er genau damit umgeht, wissen wir nicht.

                dedlfix.

                1. Blobs sind üblicherweise Objekte.

                  Ich kenne da eine andere Definition:

                  https://de.wikipedia.org/wiki/Binary_Large_Object besagt:

                  Binary Large Objects (BLOBs) sind große binäre Datenobjekte wie z. B. Bild- oder Audiodateien.

                  Äh. Nein. Ich bin nur davon ausgegangen, dass es bei den gesehenen Programmteilen keinen Unterschied macht, ob ich nun Binärkram (Blob) oder Text (Genau genommen eine Untermenge der Blobs) habe.

                  Im Übrigen behandelt JS ja alles als Object.

                  1. Hallo Raketentester,

                    Im Übrigen behandelt JS ja alles als Object.

                    Jein. Siehe mein Parallelposting von 12:48. Es gibt auch simple types, schlicht aus Performancegründen, und die werden NICHT als Objekt behandelt, solange man sie nicht als solche verwendet.

                    2 + 2 führt nicht zu einem Wrapping in Number-Objekte.

                    Richtig ist: JavaScript kann - genau wie Java, C# und andere "100%-Objekter" - mit Hilfe von Wrapperklassen so tun, als seien seine simple types Objekte.

                    Selbst die historische "alles ist ein Objekt" Sprache Smalltalk, in der sogar die Kontrollstrukturen Methoden des "Codeblock" Objekts sind, verwendet für Integers einen nativen Typ. Zumindest die Smalltalk-Version, mit der mich mein Arbeitgeber in den 90er Jahren gefoltert hat, tat das.

                    Rolf

                    --
                    sumpsi - posui - obstruxi
                  2. Tach!

                    Blobs sind üblicherweise Objekte.

                    Ich kenne da eine andere Definition:

                    Kann sein. Es geht hier aber um den Kontext Javascript im Browser, und da hat "Blob-Objekt" eine konkrete Bedeutung.

                    Im Übrigen behandelt JS ja alles als Object.

                    Ähm, nein.

                    In JavaScript, a primitive (primitive value, primitive data type) is data that is not an object and has no methods.

                    dedlfix.

                2. Hallo dedlfix,

                  jo, ist so. JavaScript unterscheidet zwischen Objekten und simple types. Simple types sind undefined, boolean, number und string (typeof null ist object).

                  Für boolean, number und string gibt es Objekt-Wrapper, die immer dann erzeugt werden, wenn auf einem solchen simple type eine Methode aufgerufen wird. Das nennt man Boxing (zumindest in .net) oder Auto-Boxing und ist etwas, das man nach Möglichkeit vermeiden sollte, denn es ist zeitaufwändig (zumindest in .net). Wenn ich auf einem String viele Methoden aufrufen will, kann es nützen, ihn vorher manuell zu boxen (zumindest in .net).

                  String.prototype.getType = function() {
                     return (typeof this) + " " + this.constructor.name;
                  }
                  
                  console.log(typeof "foo");          // string
                  console.log("foo".getType());       // Auto-Boxing, object String
                  console.log("foo".constructor.name) // Auto-Boxing!
                  
                  const foo = new String("foo");      // Wrapper-Objekt für manuelles Boxing
                  

                  Und wie gesagt: das Hinzufügen von Properties geht nur auf Objekten, nicht auf simple types.

                  Rolf

                  --
                  sumpsi - posui - obstruxi
                  1. Tach!

                    Simple types sind undefined, boolean, number und string (typeof null ist object).

                    null ist trotzdem ein primitiver Typ, auch wenn das typeof "object" liefert. Außerdem kommen noch bigint und symbol als primitive Typen hinzu.

                    dedlfix.

                    1. Hallo dedlfix,

                      danke für die Ergänzung.

                      Rolf

                      --
                      sumpsi - posui - obstruxi
  2. Hallo Michael_K,

    wenn das der JavaScript Typ "Blob" ist, kann es sein, dass der von postMessage verwendete structured clone Mechanismus dafür sorgt, dass nur die bekannten Properties eines Blobs übertragen werden.

    Falls Du es noch nicht getan hast, probier mal, den Blob zusätzlich auf die Transferliste zu setzen. Entweder als Teil der options in Syntax 1, oder als eigener Parameter in Syntax 2.

    Guckst Du hier: https://wiki.selfhtml.org/wiki/JavaScript/Window/postMessage

    Wenn Transfer auch nicht hilft, dann dürfte es interne Optimierungen der JS Engine geben, die Zusatzproperties an Blobs ignorieren. Du könntest dann versuchen, ein Wrapper-Objekt zu erzeugen. Aus Performancegründen ist ein Transfer sehr zu empfehlen, es sei denn, du brauchst den Blob auf der Senderseite nachher noch. Ob die const Deklaration einem Transfer im Wege steht, weiß ich nicht, das musst Du ausprobieren.

    const msg = { name: 'Fridolin', wert: aBlob };
    iframe.postMessage(msg);
    

    Rolf

    --
    sumpsi - posui - obstruxi
    1. Hallo Rolf,

      das war wohl nichts, Blobs sind nicht transferierbar. Das hättest Du wissen können, du Depp, Du hast den postMessage Artikel selbst geschrieben!

      Ein ArrayBuffer wäre transferierbar, aber den Blob in einen ArrayBuffer zu wandeln bedeutet ebenfalls eine Kopie. Das verbessert also nichts.

      Der Vorschlag mit dem Wrapper-Objekt führt aber zum Ziel, das habe ich gerade ausprobiert. Auf der Empfängerseite könnte man das name-Property aus dem Wrapper wieder an den Blob kleben.

      Falls die getBlobFromAFunction Funktion den Blob aus transferierbaren Objekten erzeugt, könnte es effizient sein, keinen Blob zu erzeugen. Aber das weißt nur Du, Michael.

      Rolf

      --
      sumpsi - posui - obstruxi
  3. Tach!

    Ich sende einen Blob zwischen zwei iframes (via eventListeners). Das funktioniert auch so weit ganz gut. Allerdings verliert das Blob-Object einen key, den ich vorher dem Blob noch hinzugefügt habe. Ist dieses Verhalten irgendwo beschrieben, warum und wie die Eigenschaften gelöscht werden?

    Ich vermute, dass deine Beobachtung nicht richtig ist und das Problem an einer anderen Stelle sitzt.

    Versuchsaufbau Hauptdokument

    <!DOCTYPE html>
    <script>
        function send() {
            const blob = new Blob(['blob']);
            blob.foo = 42;
            const event = new CustomEvent('customEvent', {detail: blob});
            document.querySelector('iframe').contentDocument.dispatchEvent(event);
        }
    </script>
    <button onclick="send()">Send</button>
    <iframe src="iframe.html"></iframe>
    

    iframe-Dokument

    <!DOCTYPE html>
    <script>
        document.addEventListener('customEvent', e => {
            console.log(e.detail)
        }, false);
    </script>
    

    In der Console ist bei mir zu sehen, dass das Blob-Objekt seine foo-Property immer noch hat.

    dedlfix.

    1. Hallo dedlfix,

      du hast den Aufbau fertig, probier's bitte nochmal auf die dafür vorgesehene Art und Weise mit iframe.postMessage und dem message-Event im iframe, nicht einem Custom Event.

      Rolf

      --
      sumpsi - posui - obstruxi
      1. Tach!

        du hast den Aufbau fertig, probier's bitte nochmal auf die dafür vorgesehene Art und Weise mit iframe.postMessage und dem message-Event im iframe, nicht einem Custom Event.

        Der OP schrieb nur von eventListeners, nicht dass er postMessage() verwendet. Aber damit ist das Problem nachvollziehbar. Eine nachvollziehbare Beschreibung ist immer sinnvoll, wenn man Hilfe von außenstehenden haben möchte. Dazu erstellt man sich am besten ein Minimalbeispiel, bei dem das Problem zu sehen ist.

        Jedenfalls, die postMessage-Dokumentation sagt zum Thema message-Parameter:

        The data is serialized using the structured clone algorithm. This means you can pass a broad variety of data objects safely to the destination window without having to serialize them yourself.

        This means aber auch, wie in der Dokumentation zum structured clone algorithm beschrieben, dass bestimmte Typen (u.a. Blob) explizit unterstützt werden und nicht generell alles. Dabei hat man sich wohl nur auf die öffentlich beschriebenen Dinge beschränkt, denn mit postMessage() kommen die zusätzlichen Eigenschaften nicht an.

        dedlfix.

        1. Hallo dedlfix,

          ja, das habe ich mir so gedacht. Danke für's Nachprüfen.

          Rolf

          --
          sumpsi - posui - obstruxi
          1. Hallo Rolf (und die anderen),

            vielen Dank für den Input. Ich habe es nun auch mal getestet, den Blob an einen WebWorker zu schicken mit worker.postMessage(). Auch dort kommt der Blob dann wieder "bereinigt" an. Das heisst die JS-Engine scheint wohl zu prüfen, inwiefern bei einem Object die keys/Eigenchaften definiert sind.

            Und noch einmal zum Verständnis, wie der Blob bei mir aufgeaut ist:

            const blob = new Blob([typedArray], {type: 'application/octet-binary'});
            

            Der Blob kommt auch an, nur eben ohne die zusätzlichen Eigenschaften.

            Gruss, Michael

            1. "bereinigt"

              Mangels Quelltext wissen wir immer noch nicht, ob die zusätzlichen Eigenschaften jemals im Objekt waren...

              Im Browser (aktueller Mozilla) erhalte ich mit

              <script>
              'use strict';
              
              var aBlob;
              aBlob = getBlob();
              aBlob.foo =  'tok';
              
              console.log(JSON.stringify(aBlob));
              console.log(JSON.stringify(aBlob.foo));
              
              function getBlob() {
              	const obj = {hello: 'world'};
              	const blob = new Blob([JSON.stringify(obj, null, 2)], {type : 'application/json'});
              	return blob;
              
              }
              </script>
              

              übrigens soeben

              {"foo":"tok"} 
              "tok"
              

              Da fehlt also nicht die hinzugefügte Eigenschaft. Die (und nur die) ist just vorhanden.

            2. Hallo Michael_K,

              typedArray? D.h. einer der Abkömmlinge von TypedArray? Ist ja prima - diese Dinger basieren auf einem ArrayBuffer, der Transferable ist. Bau keinen Blob, nimm einfach das buffer-Property aus dem TypedArray und schick den Buffer zum iframe.

              Mit dem ArrayBuffer wird ebenfalls kein angepappted Property kopiert. Macht aber nichts, ich finde das von mir vorgeschlagene Wrapper-Objekt dafür deutlich eleganter. Vorteil vom ArrayBuffer ist, dass er transferiert werden kann, d.h. keine Kopie erstellt werden muss. Bei kleinen Arrays ist das egal, bei Klöpsen von mehreren Megabyte ist die Sache interessanter.

              Beispielhaft so:

              function sendToIFrame(iframe, name, typedArray) {
                 const message = {
                    name: name,
                    buffer: typedArray.buffer,
                    ctorName: typedArray.constructor.name 
                 };
                 iframe.postMessage(message, *", [ message.buffer ]);
              }
              

              Im iframe kannst Du dann über ctorName den Konstruktor wiedergewinnen und das TypedArray neu erzeugen. Da ein TypedArray nichts weiter ist als ein View auf den ArrayBuffer, geht das schnell und ohne Kopieren.

              window.addEventListener("message", function(msgEvent) {
                 const message = msgEvent.data,
                       constructor = window[message.ctorName],
                       array = new constructor(message.buffer);
              });
              

              Und dann kannst Du Dir überlegen, ob Du den Namen direkt aus message.name verwenden willst oder ob Du ihn an das typed array in der Variablen array anpappst.

              Natürlich könnte man jetzt noch einen Schritt zurückgehen. Wo kommt das TypedArray her? Was steckst Du da rein? Kann man ggf. auch auf das TypedArray verzichten und direkt mit der Quelle dafür arbeiten?

              Rolf

              --
              sumpsi - posui - obstruxi
              1. Hallo Rolf,

                vielen Dank, dass werde ich mir in der Tat ueberlegen, da der Wrapper vorteile haette.

                Danke und Gruss Michael

    2. Hallo dedlfix,

      ich habe es ausführlich getestet. Die Information geht verloren (getestet mir Chromium). Ich habe es auch noch einmal testweise an einen web worker gesendet, auch dort kommt der blob dann nur bereinigt an.

      Gruss, Michael

      1. Hallo Michael_K,

        hast Du schon meinen Vorschlag mit Wrapper-Objekt und direkter Verwendung des ArrayBuffer aus dem TypedArray gesehen? Der Blob ist für die Übertragung nicht das beste Mittel.

        Rolf

        --
        sumpsi - posui - obstruxi
      2. Tach!

        ich habe es ausführlich getestet.

        Das Problem ist, wenn du nicht nachvollziehbar beschreibst, was du tust, ist es schwer für Antwortende, das Problem zu erkennen. Dass du postMessage() verwendest, hast du nicht geschieben. postMessage() hat laut Beschreibung einen eingebauten Konverter für diverse Objekte. Wenn du aber stattdessen ein Custom Event versendest, wird nichts konvertiert.

        dedlfix.

        1. Hallo dedlfix,

          da fällt mir ein: im iframe ein Event zu triggern setzt gleichen Origin voraus. Aber ob das so ist, wissen wir ja auch nicht. Und ob für den Anwendungsfall der Transfer-Mechanismus von postMessage sinnvoll ist, wissen wir auch nicht.

          Ist schon Mist, dass im Forum so viel Sand rumliegt. Bei dem Wind, den hier einige machen, bläst das immer die Glaskugeln trübe... 😉

          Rolf

          --
          sumpsi - posui - obstruxi