Rolf B: kleines Script

Beitrag lesen

Hallo tbe,

IIFEs in allen Ehren, aber du übertreibst ein bisschen damit 😀

var f = (function(){
   ...
})();

Die brauchst Du nicht, das Ganze ist ja schon in eine Function gekapselt die an onload zugewiesen ist.

Bei der inneren IIFE fände ich es besser, wenn das eine eigenständige Funktion wäre. Allerdings etwas umsortiert. Schleifensteuerung und Bildung des Dateinamens nach außen, und dann eine Funktion, die sich NUR um den XMLHttpRequest kümmert. Das Prinzip heißt Aufgabentrennung (oder Separation of Concerns) und ist ein wichtiges Entwurfsprinzip für Programme. Es gibt Sprachen, in denen Funktionsaufrufe teuer sind, aber JavaScript gehört nicht dazu.

Unabhängig von diesen strukturellen Überlegungen gibt es auch noch einen Hinweis zur Serverbelastung. Du schickst definitiv 20 HTTP Requeste los, egal wieviele Dateien vorhanden sind. Das hat den Vorteil, dass dadurch die maximale Anzahl an parallelen Requests unterwegs ist. Es hat aber den Nachteil, dass der Server bei 5 Files 15 unnötige Requeste bekommt.

Deswegen schlug Jürgen ja vor, die nächste Datei erst anzufordern wenn Du die Antwort vom vorigen Request hast. Man KANN sich eine Mischform denken, bei der Du mehrere XMLHttpRequest Objekte hast und mehrere Dateien parallel anforderst. Immer, wenn ein Request zurückkehrt, schickst Du über ihn die nächste Anforderung los. Sobald einer HTTP 404 liefert, ist Schluss.

Ich habe da mal was vorbereitet…

   function fetchFiles() {
      var fileNumber = 0;
      ["Adam", "Bernd", "Cäsar", "Dieter"].forEach(function(name) {
         var req = createRequest(name);
         fetchNextFile(req);
      });   
      
      function createRequest(name) {
         var request = new XMLHttpRequest();
         request["x-name"] = name;
         request.addEventListener("load", handleFileReceived);
         return request;
      }
      
      function fetchNextFile(req) {
         if (fileNumber < 0) 
            return;
         fileNumber++;
         var fileName = "file_"+("000"+fileNumber).substr(-3)+".txt";
         
         console.log(req["x-name"] + ": Start downloading " + fileName);
         req["x-fileNumber"] = fileNumber;
         req["x-fileName"] = fileName;
         req.open("GET", fileName, true);
         req.send();
      }
      
      function handleFileReceived() {
         if (this.status == 404) {
            fileNumber = -1;
            console.log("No more files.");
         }
         else if (this.status == 200 || this.status == 304) {
            var receivedText = this.responseText;
            var receivedNumber = this["x-fileNumber"];
            var receivedName = this["x-fileName"];
            
            fetchNextFile(this);   // Vor der Verarbeitung schonmal den nächsten Fetch losschicken
            
            verarbeite_html(this["x-name"], receivedNumber, receivedName, receivedText);
         }
      }
      function verarbeite_html(workerName, fileNumber, fileName, text) {            
         console.log("Datei #" + fileNumber + " (" + fileName + ") von " + 
                     workerName + " empfangen, " + text.length + " Zeichen.");
      }
   }

Die Funktion erzeugt 4 XMLHttpRequests und erweitert sie um drei private Eigenschaften. Das würde man normalerweise über Symbol-Objekte machen, aber ich bin ein IE11-Junkie und der kann keine Symbols. Darum heißen die Eigenschaften x-name, x-fileNumber und x-fileName. Diese Eigenschaften sind nötig, damit der load-Eventhandler weiß, was er da bekommen hat. Es werden ja 4 Handler parallel laufen, und es gibt nur eine Handler-Funktion :). Statt der x-Eigenschaften könnte man sich auch ein Kapselobjekt je XMLHttpRequest denken, das diese Infos enthält.

In createRequest wird dem Requestobjekt ein Name gegeben (just for fun) und gleich der erste Request gestartet. Die Steuerung, welche Datei abgerufen wird, erfolgt über eine Variable in fetchFiles, d.h. aus Sicht der Arbeitsfunktionen eine globale Variable. JavaScript führt Code rein sequenziell aus, es gibt daher keine Synchronsisierungsprobleme beim Zugriff auf diese Variable.

Das Starten eines Requests erfolgt über fetchNextFile. Hier wird der Dateiname gebildet, die Dateinummer und der -name in den privaten Eigenschaften des Requestobjekts gespeichert und der Request gestartet (open und send). Send wartet nicht, d.h. die vier createRequest-Aufrufe erfolgen direkt hintereinander, während die Requestdaten gerade durch die Leitung flutschen.

Irgendwann trifft die erste Antwort ein und auf dem betreffenden Handler wird das load Event gefeuert. Wichtig ist auch hier: Solange dieses Load-Event verarbeitet wird, müssen weitere eintreffende Load-Events warten. Es kann keinen Konflikt zwischen zwei "gleichzeitig" laufenden Eventhandlern geben. Pro Load-Event wird handleFileReceived aufgerufen. Die Funktion prüft, ob Status 404 zurückkam und setzt dann fileNumber auf -1, als Abbruchsignal. Bei Status 200 oder 304 wird der responseText sowie Dateinummer und -name in Variablen gelegt und der nächste Request gestartet, damit der Server sich nicht langweilt. Danach wird der empfangene Text verarbeitet. Das Kopieren in Variablen ist wichtig, weil das XMLHttpRequest Objekt durch fetchNextFile schon für den nächsten Request verwendet wird, während die empfangenen Daten noch verarbeitet werden.

Rolf

--
sumpsi - posui - clusi