Select Page

Große Dateien mit der JavaScript File-Api verarbeiten

by | Apr 12, 2012 | Development

Die JavaScript File-Api ist für mich (zusammen mir der Drag&Drop Api) etwas, das gemischte Gefühle erzeugt. Es ist unheimlich praktisch, Dateien clientseitig Einlesen und Verarbeiten zu können. Trotzdem  hat sowohl die Schnittstelle, als auch deren Implementierung ein paar Strickfallen, vor allem wenn die Dateigröße keine Rolle spielen soll.
Ein kleines Beispiel:
Ein einfaches Beispiel der File-API habe ich mal in jsfiddle zum ausprobieren angelegt. Hier sieht man schnell, wie man Dateien in der File-Api einliest, um sie später verarbeiten zu können
Folgende Funktion wird aufgerufen, wenn eine Datei ausgewählt wird:
var uploadHdl = function(ev) {
   var input = ev.target;
   var fileToRead = input.files[0];
    //neuen Reader erzeugen
    var reader = new FileReader();
reader.addEventListener("load",function(loadEv) {
      // hier liegt ein ArrayBuffer mit dem Dateiinhalt und
      // kann weiterverarbeitet werden
      alert("Datei eingelesen: "+loadEv.target.result.byteLength+" bytes");
})
reader.readAsArrayBuffer(fileToRead);
}

Und bis hier gibt es auch noch nicht viel an der Schnittstelle zu meckern, ausser vielleicht dass der FileReader keine Information in seinem onload event mitgibt, welche Datei er gerade gelesen hat. Daran geht die Welt aber nicht unter.
Was mich persönlich allerdings stört, ist dass das obige Beispiel nicht skaliert: Verwendet man eine Datei mit mehreren 100 mb oder sogar ein paar GB, sollte man in Deckung springen: Der Browser wird es nicht überleben.

Die Ursache liegt im Browser
Schaut man sich den Sourcecode von Chrome oder Spidermonkey an, findet man die Übeltäter (Stand April 2012):
– Chrome (chromium/src/third_party/WebKit/Source/WebCore/fileapi/FileReaderLoader::didReceiveResponse)
unsigned long long length = response.expectedContentLength();
...
m_rawData = ArrayBuffer::create(static_cast<unsigned>(length), 1);

– Spidermonkey (content/base/src/nsDOMFileReader::ReadFileContent): 
mTotal = mozilla::dom::kUnknownSize;
mFile->GetSize(&mTotal);
...
mResultArrayBuffer = js_CreateArrayBuffer(aCx, mTotal);

Beide Browser legen zu Beginn des Lesevorganges einen ArrayBuffer mit der Größe der kompletten Datei an. Ich denke hier ging es den Entwicklern darum, Benutzer davor zu bewahren mit Teildaten arbeiten zu müssen (der Standard empfiehlt hier nämlich, progress events mit den Teildaten zurückzugeben). Allerdings greifen die Schutzmechanismen des Browsers, sobald man zuviel Speicher im Heap reservieren will und die Applikation wird abgebrochen.
File.slice() zur Rettung
Allerdings kann ich die Browserentwickler auch verstehen: 90% der FileApi User werden wohl kleinere Dateien (und bis 10Mb ist man da sicher) verarbeiten, diesen wird damit die Arbeit erleichtert. Und auch die Implementierung an sich ist einfacher: Man muss nur einmal Speicher reservieren und kennt die Dateigröße ja bereits im voraus.
Doch auch wer große Dateien bearbeiten will, z.B. um Checksummen zu berechnen, findet die rettende Lösung: Die File.slice() Methode erlaubt einen, große Dateien in kleine Teilstücke zu zerlegen und danach separat einzulesen. Das ist zwar irgendwie durch die Brust ins Auge (für mich gehört das zum Reader), aber besser als nichts.
Firefox und Chrome haben diese Funktionen allerdings derzeit nur mit webkit- oder moz Prefix, dadurch bläht sich der Code leider wieder auf. Ich habe eine zweite Version meines Beispiels erstellt, das nur die Hälfte der Datei einliest.

// Firefox: slice ist eine neue Datei mit der hälfte der Daten von fileToRead:
var slice = fileToRead.mozSlice(0,Math.round(fileToRead.size/2));

Fazit:
Die File-Api ist, wenn man wirklich im großen Maßstab denkt (und als JS Entwickler muss man das mittlerweile) leider etwas halbgar. Hier sind allerdings alle Seiten ein wenig Schuld: Der Standard schlägt zwar einen streamorientierten Ansatz vor wie er bei anderen File-Apis üblich ist, macht diesen jedoc optional. Die Browserentwickler wollen den Standard natürlich sehr früh und gleichzeitig robust implementieren, nehmen also erstmal die Variante die für sie am einfachsten ist. Und die Endnutzer wollen eine Api, die einfach den Inhalt der Datei zurückgibt, ohne sich über Memory management Gedanken machen zu müssen (…müssen Sie jetzt aber trotzdem).
Und natürlich: Da ich den Standard nicht mit Entworfen oder Implementiert habe, kann es gut möglich sein, dass ich berechtigte Gründe übersehe, warum es heute so ist, wie es ist.
Eins wird aber klar: Die hohe Geschwindigkeit, mit der neue Standards (auch wenn sie noch vorläufig sind) hat ihren Preis – die gute alte Browserweiche, sei sie auch nur ein ‘Moz’ Prefix, bleibt uns wohl auch die nächsten Jahre nicht erspart.
Zum Weiterlesen:

0 Comments

Submit a Comment

Your email address will not be published. Required fields are marked *

More posts on the topic Development

Mein PHP-Trainingsprojekt

PHP Schulung Vor kurzem haben wir begonnen, eine neue Programmiersprache zu lernen – PHP. In der ersten Woche haben wir mit den Grundlagen wie Variablen, Arrays, Schleifen begonnen und uns schrittweise zu komplizierterer Syntax wie Funktionen, Objekten und Klassen...

check_prometheus ist jetzt öffentlich verfügbar!

Monitoring ist komplex, das wissen wir hier bei NETWAYS leider zu gut. Deswegen laufen in der Infrastruktur auch mal gerne mehrere Tools für die Überwachung. Zwei gern gesehene Kandidaten sind dabei Icinga und Prometheus. Icinga und Prometheus erfüllen...