Nico R.: Warum steht Variable in Funktion nicht zur Verfügung (Frage zum Wiki-Artikel „File Upload“)

problematische Seite

Hallo zusammen,

ich versuche gerade das Script auf SelfHTML zum File Upload etwas anzupassen. Ich möchte erreichen, dass die Bild-Informationen name und size nicht in einer separaten Liste (dateiListe), sondern direkt unter dem jeweiligen Bild (in thumblist) erscheinen.

Dabei stoße ich auf das Problem, dass var=f aus der for-Schleife im FileReader nicht zur Verfügung steht. Auf var="test" kann ich dagegen zugreifen. Ich stehe irgendwie auf dem Schlauch... Warum ist das so?

function dateiauswahl(evt) {

  var files = evt.target.files;

  for (var i = 0, f; f = files[i]; i++) {
    var test = "test";
    var reader = new FileReader();
    reader.onload = (function (theFile) {
      return function (e) {
        console.log(f); // Ausgabe: undefined
        console.log(test); // Ausgabe: test
      };
    })(f);
  }
}

Schöne Grüße

Nico

  1. problematische Seite

    @@Nico R.

    ich versuche gerade das Script auf SelfHTML zum File Upload etwas anzupassen.

    Der auch schon etwas in die Jahre gekommen ist. Gibt es überhaupt noch einen Anwendungsfall für das Schlüsselwort var? Das sollte sich durch let bzw. const ersetzen lassen.

    Und noch was sollte ersetzt werden: evt. Da sollte ein sprechender Variablenname verwendet werden: event. (Und nein, die Ausrede ‚Man weiß doch, was gemeint ist‘ zählt in einem Tutorial nicht. In Produktivcode auch nicht.)

    Dabei stoße ich auf das Problem

    Wo kann man sich dein Problem denn ansehen? Ohne erstmal eine laufende Testumgebung erstellen zu müssen – das ist deine Aufgabe.

    Kwakoni Yiquan

    --
    Ad astra per aspera
    1. problematische Seite

      Servus!

      @@Nico R.

      ich versuche gerade das Script auf SelfHTML zum File Upload etwas anzupassen.

      Der auch schon etwas in die Jahre gekommen ist. Gibt es überhaupt noch einen Anwendungsfall für das Schlüsselwort var? Das sollte sich durch let bzw. const ersetzen lassen.

      Vielen Dank für deinen Hinweis. Und noch größeren Dank an @Rolf B, der das Beispiel modernisiert hat.

      Der ganze Artikel ist ein ToDo seit Januar 2023

      @Robert B. hatte ihn schon mal durchgeschaut Baustellen im Wiki: Datei Upload

      Ich bitte einfach drum, dass sich irgendjemand die Beispiele vornimmt und sie entweder auf ES6 modernisiert, labels statt p verwendet oder eben depubliziert.

      Letztes Beispiel könnte ja das neue HTML/Attribute/capture für ein SELfie beinhalten.

      Als Fortsetzung dient ja der Fortgeschrittenen-Artikel: PHP/Tutorials/File-Upload

      Vielen Dank im Voraus!

      Herzliche Grüße

      Matthias Scharwies

      --
      Was ist eine Signatur?
  2. problematische Seite

    Hallo Nico R.,

    das liegt daran, dass Du var und nicht let nimmst, zusammen mit Deiner kaputten Schleifensteuerung und dem falsch gemachten Versuch, die Probleme von var zu umgehen.

    Aber der Reihe nach.

    Variablen mit var werden "gehoben", d.h. i, f, test und reader werden behandelt, als wären sie vor der Schleife deklariert worden.

    Sowas ist ungünstig, wenn man sie in einer Closure einschließt und in einer Schleife mehrere davon bildet. Die Closure ist die Funktion, die von deiner function(theFile) zurückgegeben wird und f einschließt.

    Das ist aber immer die gleiche Variable, und weil deine Schleife endet, wenn du hinter das Ende des files-Arrays kommst und f undefined wird, ist bei Ausführung des load Events f immer undefined.

    Du verwendest bereits eine IIFE um das Problem zu lösen. Damit schaffst du einen neuen Closure-Kontext pro Durchlauf der Schleife. Du musst jetzt nur noch theFile statt f verwenden und dann passt es.

    Besser ist aber: verwende let statt var. Variablen mit let werden nicht gehoben, und ihr Geltungsbereich ist nicht die Funktion, in der sie stehen, sondern der {...}-Block. Du bekommst also pro Durchlauf einen neuen Variablensatz und kannst auf die Hilfsfunktion, die theFile erzeugt, verzichten.

    Und dann noch dies: die Anzahl der Schleifendurchläufe solltest du über i<files.length steuern, nicht darüber dass Javascript undefined liefert, wenn du hinter dem Ende des Arrays zugreifst.

    Rolf

    --
    sumpsi - posui - obstruxi
    1. problematische Seite

      Hallo Rolf,

      ich weiß gar nicht, wo ich anfangen soll. Das kommt davon, wenn man zu viel fremden Code einfach kopiert, ohne ihn zu verstehen.

      Und dann noch dies: die Anzahl der Schleifendurchläufe solltest du über i<files.length steuern, nicht darüber dass Javascript undefined liefert, wenn du hinter dem Ende des Arrays zugreifst.

      Richtig. Das war eigentlich der Grund des Übels. Das mache ich sonst so gar nicht, ich kannte das Prinzip nicht mal. Mir ist das leider erst jetzt aufgefallen, und erst jetzt lese ich auch deine Bemerkung dazu.

      Das mit dem "gehoben" werden von var war mir gar nicht so bewusst. Vermutlich auch, weil ich in Schleifen eigentlich generell nur noch let verwende. Aber das dürfte in diesem Fall nicht der alleinige Grund gewesen sein...

      Das ist aber immer die gleiche Variable, und weil deine Schleife endet, wenn du hinter das Ende des files-Arrays kommst und f undefined wird, ist bei Ausführung des load Events f immer undefined.

      Normalerweise hätten ja die ersten vorhandenen Array-Einträge auch mit var angezeigt werden müssen. Das "Problem" war zusätzlich, dass reader.readAsDataURL(f) (das fehlte in meinem geposteten Script), asynchron abläuft, richtig? Das heißt, in dem Moment als das Ding die Dateiinformationen bzw. das Vorschaubild anzeigen sollte, war die Schleife schon durch und lieferte durch var (fälschlich) für alle drei onloads undefined. Mit let funktioniert das aber offensichtlich trotz asynchroner Ausführung trotzdem korrekt. Hab ich das richtig verstanden?

      Zu Vollständigkeit - das korrigierte und gekürzte Codestück sieht dann jetzt so aus:

      function dateiauswahl(event) {
        let files = event.target.files;
      
        for(let i=0; i<files.length; i++) {
          let f = files[i];
          let reader = new FileReader();
      
          reader.addEventlistener("load", function(event) {
            console.log(f.name); // Ausgabe: Dateiname
            img.src = event.target.result; // Vorschaubild
          });
        } 
      
        reader.readAsDataURL(f);
      }
      

      Besten Dank und schöne Grüße

      Nico

      1. problematische Seite

        Lieber Nico,

        Zu Vollständigkeit - das korrigierte und gekürzte Codestück sieht dann jetzt so aus:

        function dateiauswahl(event) {
          ...
          for(let i=0; i<files.length; i++) {
            ...
            let reader = new FileReader();
            ...
          } 
        
          reader.readAsDataURL(f);
        }
        

        da verwendest Du innerhalb der Schleife eine nur dort existierende Variable reader. Wenn Du sie dann am Ende außerhalb der Schleife verwenden willst, gibt es die dort natürlich nicht.

        Das Umkopieren von event.target.files zu files ist nur scheinbar eine Erleichterung. Man möchte manchmal wissen, ob man es mit einem Array, oder einer anderen Art von Liste zu tun hat. Das wird bei der Variable files nicht deutlich. Man kann aber event.target.files ansehen, dass es ein Array sein muss.

        Liebe Grüße

        Felix Riesterer

        1. problematische Seite

          Hi Felix,

          da verwendest Du innerhalb der Schleife eine nur dort existierende Variable reader. Wenn Du sie dann am Ende außerhalb der Schleife verwenden willst, gibt es die dort natürlich nicht.

          Stimmt. Das war falsch rauskopiert. In meinem laufenden Script ist reader.readAsDataURL() ebenfalls Teil der for-Schleife.

          Das Umkopieren von event.target.files zu files ist nur scheinbar eine Erleichterung. Man möchte manchmal wissen, ob man es mit einem Array, oder einer anderen Art von Liste zu tun hat. Das wird bei der Variable files nicht deutlich. Man kann aber event.target.files ansehen, dass es ein Array sein muss.

          Wie gesagt, das war ein Überbleibsel aus dem Tutorial-Script.

          Hier der nochmals geänderte Ausschnitt. forEach mag ich irgendwie nicht, ich komme mit herkömmlichen for-Schleifen besser klar. Rolfs Vorschlag mit for in führt übrigens in reader.readAsDataURL(file) zu folgender Fehlermeldung: Uncaught TypeError: FileReader.readAsDataURL: Argument 1 does not implement interface Blob.

          Laut Konsole wird bei for in nicht nur ein File-Objekt so wie bei for, sondern auch ein function item() erzeugt. Liegts daran? Oder hab ichs falsch angewendet?

          function dateiauswahl(event) {
            for(let i=0; i<event.target.files.length; i++) {
            //for(let i in event.target.files) { => führt zu Fehlermeldung in reader.readAsDataURL(file)
              const file = event.target.files[i];
              const reader = new FileReader();
          
              reader.addEventlistener("load", function(event) {
                console.log(file.name); // Ausgabe: Dateiname
                img.src = event.target.result; // Vorschaubild
              });
          
              reader.readAsDataURL(file);
            } 
          }
          

          Schöne Grüße

          Nico

          1. problematische Seite

            @@Nico R.

            Rolfs Vorschlag mit for in

            Rolf sagte doch for of

              for(let i=0; i<event.target.files.length; i++) {
              //for(let i in event.target.files) { => führt zu Fehlermeldung in reader.readAsDataURL(file)
                const file = event.target.files[i];
                const reader = new FileReader();
            
            for (const file of event.target.files) {
                const reader = new FileReader();
            

            Kwakoni Yiquan

            --
            Ad astra per aspera
            1. problematische Seite

              Hallo Gunnar,

              yup, tat ich. Wer for...of nicht kennt, mag dazu neigen, das zu überlesen.

              Es ist aber auch gemein von JavaScript: for...in iteriert die Eigenschaftsnamen von Objekten, for...of den Inhalt von iterierbaren Objekten.

              Rolf

              --
              sumpsi - posui - obstruxi
              1. problematische Seite

                Richtig, ich habs überlesen. Bzw. kannte ich for of nicht. Oder kannte ichs und habs immer schon mit for in verwechselt? Oder umgekehrt? Ich weiß es nicht. Auf jeden Fall wieder was dazugelernt 👍

                Schöne Grüße

                Nico

      2. problematische Seite

        @@Nico R.

        Zu Vollständigkeit - das korrigierte und gekürzte Codestück sieht dann jetzt so aus:

        Korrigiert heißt nicht korrekt.

        function dateiauswahl(event) {
          let files = event.target.files;
        

        files wird im weiteren Verlauf nicht mehr geändert, also ist const angebracht, nicht let. Außerdem: warum event.target.files überhaupt in eine lokale Variable umkopieren? Man kann (sollte) event.target.files verwenden.

          for(let i=0; i<files.length; i++) {
            let f = files[i];
            let reader = new FileReader();
        

        i ist als Schleifenindex durchaus üblich, aber f sollte eine sprechende Variable sein: file.

        Auch hier const statt let, wenn überhaupt. Das beides im Folgenden jeweils nur einmal vorkommt, braucht man dafür auch keine lokalen Variablen.

        Kwakoni Yiquan

        --
        Ad astra per aspera
        1. problematische Seite

          files wird im weiteren Verlauf nicht mehr geändert, also ist const angebracht, nicht let. Außerdem: warum event.target.files überhaupt in eine lokale Variable umkopieren? Man kann (sollte) event.target.files verwenden.

          Ja, stimmt. Das war noch ein Überbleibsel aus dem Tutorial-Script. Ich verwende das inzwischen ohne Zwischenvariable.

          i ist als Schleifenindex durchaus üblich, aber f sollte eine sprechende Variable sein: file.

          Seh ich genauso. Auch das noch ein Überbleibsel

          Auch hier const statt let, wenn überhaupt. Das beides im Folgenden jeweils nur einmal vorkommt, braucht man dafür auch keine lokalen Variablen.

          Ist geändert ;-)

    2. problematische Seite

      Lieber Rolf,

      Variablen mit var werden "gehoben", d.h. i, f, test und reader werden behandelt, als wären sie vor der Schleife deklariert worden.

      das wäre alles nicht der Rede wert, wenn man anstelle einer Schleife mit Array.forEach gearbeitet hätte.

      Ein typischer Fall, bei dem es sich lohnt, von alten Gewohnheiten Abstand zu nehmen. Wir hatten das schon einmal...

      Im vorliegenden Fall könnte das dann so (oder so ähnlich) aussehen:

      function dateiauswahl(evt) {
      
        evt.target.files.forEach(aFileInput => {
          const reader = new FileReader();
      
          reader.readAsText(aFileInput);
      
          reader.onload = function() {
            console.log(reader.result);
          }
      
          reader.onerror = function() {
            console.log(reader.error);
          };
        });
      }
      

      Liebe Grüße

      Felix Riesterer

      1. problematische Seite

        Hallo Felix,

        wenn man anstelle einer Schleife mit Array.forEach gearbeitet hätte.

        kann man hier nicht direkt, weil files ein FileList-Objekt ist und kein Array. Die NodeList hat mittlerweile forEach spendiert bekommen, die FileList nicht.

        Statt dessen besitzt die FileList einen Iterator, deshalb kann man die for...of Schleife verwenden. Das ist jedenfalls besser als ein Array.prototype.forEach.call(...)-Konstrukt. Dein damaliges Argument mit dem Scope der Callback-Funktion zieht übrigens nicht, weil die Schleifenvariable zum Scope des Schleifenblocks gehört. Eine Funktion ist zur Scope-Abgrenzung deshalb unnötig, wenn man const und let nutzt.

        Ich habe das Wiki-Beispiel entsprechend überarbeitet. Die Liste der Dateien stelle ich dann auch gleich als Liste dar.

        Rolf

        --
        sumpsi - posui - obstruxi
        1. problematische Seite

          Hallo,

          die for ... of Variante hat auch den Vorteil, dass bei Bedarf await, continue, break, etc. zur Verfügung stehen. Mit forEach wäre ich immer sehr vorsichtig bzw. würde immer .map() bevorzugen. Mit dem Spread Operator (...) kann man die FileList elegant in ein Array wandeln. Und da ich weiter oben etwas von Lesbarkeit gelesen habe, würde ich die asynchronen Dinge fein säuberlich auslagern ;-)

          Also etwa so:

          async function dateiauswahl(evt) {
          
              const fileArray = [...evt.target.files];
              // den Speicher schonend nacheinander einlesen
              for(const file of fileArray) {
                  let fileText = await dateiLesenAsync(file);
                  // conntinue, break, etc. sind verfügbar
              }
          
              // oder alles auf einmal
              let fileTextArray = await Promise.all(fileArray.map(file => dateiLesenAsync(file)));
          }
          
          async function dateiLesenAsync(file) {
              return new Promise((resolve, reject) => {
                  const reader = new FileReader();
                  reader.onload = function () {
                      resolve(reader.result);
                  }
                  reader.onerror = function (error) {
                      reject(error)
                  };
                  reader.readAsText(file);
              });
          }