Start · Sprachen · JavaScript · Referenz · FileSystemWritableFileStream

FileSystemWritableFileStream

Schnittstelle

Ein <code>WritableStream</code> mit den Zusatzmethoden <code>write()</code>, <code>seek()</code> und <code>truncate()</code>, der in genau eine Datei des Dateisystems schreibt.

seit JavaScript Chromium 86+, Firefox 111+ (nur OPFS), Safari 26+ Kategorie: webapi

Signatur

interface FileSystemWritableFileStream extends WritableStream

Beschreibung

FileSystemWritableFileStream gehört zur File System API. Du bekommst ein solches Objekt nicht über einen Konstruktor, sondern über FileSystemFileHandle.prototype.createWritable(). Das Handle stammt entweder aus window.showSaveFilePicker() bzw. showOpenFilePicker() (File System Access API, Dateien des Nutzers) oder aus dem Origin Private File System (navigator.storage.getDirectory()).

Die Schnittstelle erbt von WritableStream. Du kannst also mit getWriter() und pipeTo() arbeiten. Dazu kommen drei Komfortmethoden:

  • write(data): schreibt ein ArrayBuffer, TypedArray, DataView, einen Blob oder einen String an die aktuelle Position. Stattdessen geht auch ein Befehlsobjekt { type: "write" | "seek" | "truncate", data, position, size }.
  • seek(position): setzt den Dateizeiger auf ein bestimmtes Byte.
  • truncate(size): kürzt oder verlängert die Datei auf size Bytes.

Alles wird zuerst in eine temporäre Swap-Datei geschrieben. Erst close() überträgt die Änderungen atomar in die Zieldatei. Rufst du stattdessen abort() auf oder vergisst du close(), bleibt die Originaldatei unverändert. Das eignet sich für Editoren, Exportfunktionen und Web-Apps, die lokale Dateien speichern wollen, ohne jedes Mal einen Download auszulösen.

Parameter

Name Typ Default Beschreibung
$data Pflicht ArrayBuffer | TypedArray | DataView | Blob | string | {type: "write"|"seek"|"truncate", data?, position?: number, size?: number} Parameter von write(): die Daten oder ein Befehlsobjekt. Bei type: "write" mit position wird ab diesem Byte geschrieben.
$position Pflicht number Parameter von seek(): Byte-Offset ab Dateianfang, auf den der Dateizeiger gesetzt wird.
$size Pflicht number Parameter von truncate(): neue Dateigröße in Bytes. Wird die Datei dadurch länger, wird mit Nullbytes aufgefüllt.

Beispiele

Text über den Speichern-Dialog in eine Datei schreiben

const saveText = async (text) => {
  // Muss durch eine Nutzeraktion ausgelöst werden (z. B. Klick)
  const handle = await window.showSaveFilePicker({
    suggestedName: 'notiz.txt',
    types: [{ description: 'Textdatei', accept: { 'text/plain': ['.txt'] } }]
  });
  const writable = await handle.createWritable();
  await writable.write(text);
  await writable.close(); // erst jetzt landet der Inhalt in der Datei
  console.log(`Gespeichert: ${handle.name}`);
};

document.querySelector('#save').addEventListener('click', () => saveText('Hallo Welt'));
Gespeichert: notiz.txt

Im Origin Private File System mit seek, truncate und Befehlsobjekten arbeiten

const root = await navigator.storage.getDirectory();
const handle = await root.getFileHandle('log.txt', { create: true });

// keepExistingData: true übernimmt den bisherigen Inhalt in die Swap-Datei
const writable = await handle.createWritable({ keepExistingData: true });
await writable.write('ABCDEFGHIJ');
await writable.seek(2);
await writable.write('xy');                              // ABxyEFGHIJ
await writable.write({ type: 'write', position: 8, data: 'Z' }); // ABxyEFGHZJ
await writable.truncate(9);                               // ABxyEFGHZ
await writable.close();

const file = await handle.getFile();
console.log(await file.text());
ABxyEFGHZ

Download per pipeTo() direkt in eine Datei streamen

const downloadTo = async (url) => {
  const handle = await window.showSaveFilePicker({ suggestedName: 'video.mp4' });
  const writable = await handle.createWritable();
  const response = await fetch(url);
  // pipeTo() schließt den Stream am Ende automatisch
  await response.body.pipeTo(writable);
  console.log('Download abgeschlossen');
};
Download abgeschlossen

Änderungen bei einem Fehler verwerfen

const root = await navigator.storage.getDirectory();
const handle = await root.getFileHandle('config.json', { create: true });
const writable = await handle.createWritable();
try {
  const json = JSON.stringify({ theme: 'dark', version: 2 }, null, 2);
  await writable.write(json);
  await writable.close();
} catch (err) {
  await writable.abort(); // Originaldatei bleibt unverändert
  console.error('Speichern fehlgeschlagen:', err);
}

// Wichtig · Fallstricke

  • Browser-Unterstützung: showSaveFilePicker() und showOpenFilePicker() gibt es nur in Chromium-Browsern (Chrome, Edge, Opera). Firefox und Safari unterstützen createWritable() nur für Dateien im Origin Private File System. Prüfe vorher, ob die Funktion existiert, z. B. mit 'showSaveFilePicker' in window.
  • Ohne close() passiert nichts: Bis close() aufgerufen wird, landet alles nur in der Swap-Datei. Schließt der Tab vorher, gehen die Daten verloren.
  • Bestehender Inhalt: Standardmäßig startet der Stream mit einer leeren Datei. Mit createWritable({ keepExistingData: true }) bleibt der bisherige Inhalt erhalten, etwa zum Anhängen oder Überschreiben einzelner Bytes.
  • Sperren: Solange ein Writable offen ist, kann es zu einer NoModificationAllowedError kommen, z. B. wenn gleichzeitig ein FileSystemSyncAccessHandle auf dieselbe Datei geöffnet ist.
  • Berechtigungen und Sicherheit: Die API funktioniert nur über HTTPS. Picker brauchen eine Nutzeraktion. Für Schreibzugriff auf Nutzerdateien muss die Berechtigung readwrite erteilt sein (handle.requestPermission({ mode: 'readwrite' })). Chromium prüft die Swap-Datei beim close() unter Umständen mit Safe Browsing. Deshalb kann das Schließen bei großen Dateien spürbar dauern.
  • Performance: Viele kleine write()-Aufrufe sind langsam. Sammle die Daten lieber in einem Blob oder verwende pipeTo(). Für sehr häufige, synchrone Zugriffe im Worker ist FileSystemSyncAccessHandle schneller.