Warum steht Variable in Funktion nicht zur Verfügung (Frage zum Wiki-Artikel „File Upload“)
Nico R.
- frage zum wiki
- javascript
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
@@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
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 durchlet
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
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
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
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
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
zufiles
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 Variablefiles
nicht deutlich. Man kann aberevent.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
@@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
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
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
@@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
files
wird im weiteren Verlauf nicht mehr geändert, also istconst
angebracht, nichtlet
. Außerdem: warumevent.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, aberf
sollte eine sprechende Variable sein:file
.
Seh ich genauso. Auch das noch ein Überbleibsel
Auch hier
const
stattlet
, wenn überhaupt. Das beides im Folgenden jeweils nur einmal vorkommt, braucht man dafür auch keine lokalen Variablen.
Ist geändert ;-)
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
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
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);
});
}