Obsługa plików i operacje wejścia/wyjścia w JavaScript
W JavaScripcie uruchomionym w przeglądarce nie masz swobodnego dostępu do dysku komputera (to celowe – kwestia bezpieczeństwa). Mimo to da się realizować wiele praktycznych operacji wejścia/wyjścia (I/O): wczytywać pliki od użytkownika, odczytywać je jako tekst lub dane binarne, przetwarzać, a potem zapisywać wynik jako nowy plik do pobrania. Poniżej masz kompletne podstawy + gotowe przykłady z formularzem.
1) Co oznacza I/O (wejście/wyjście) w programowaniu?
Wejście (Input) to pobieranie danych z zewnątrz programu, np. z klawiatury, formularza, pliku, sieci. Wyjście (Output) to wysyłanie danych na zewnątrz, np. do ekranu, do pliku, do sieci.
W przeglądarce typowe I/O to m.in.:
- odczyt danych z formularzy (input/textarea/select),
- wczytanie pliku poprzez
<input type="file">, - zapis wyniku jako plik do pobrania (Blob + link download),
- (opcjonalnie) zapis małych danych w
localStorage/indexedDB.
2) Dlaczego „czysty JS” nie zapisuje plików gdzie chce?
Przeglądarka działa w sandboxie. Kod strony nie może bez pytania użytkownika: przeszukiwać dysku, czytać dowolnych plików ani zapisywać ich w wybranym katalogu. Dlatego w przeglądarce typowy scenariusz wygląda tak:
- Użytkownik wybiera plik w formularzu (to jest świadoma zgoda).
- JS czyta zawartość pliku (tekst lub binarnie).
- JS tworzy nowy plik i udostępnia go do pobrania.
3) Formularz do obsługi plików (wejście/wyjście)
Poniżej jest prosty formularz, który:
- wczytuje plik tekstowy lub binarny,
- wyświetla podgląd (dla tekstu),
- pozwala zapisać wynik jako nowy plik (TXT lub BIN).
<form id="fileForm">
<label>Wybierz plik:</label><br>
<input id="fileInput" type="file" accept=".txt,.csv,.json,.bin,image/*" />
<div style="margin-top:10px">
<label>Tryb odczytu:</label>
<select id="mode">
<option value="text">Tekst (FileReader.readAsText)</option>
<option value="binary">Binarny (ArrayBuffer)</option>
</select>
</div>
<div style="margin-top:10px">
<button type="submit">Wczytaj</button>
</div>
</form>
<hr>
<h3>Podgląd / wynik</h3>
<textarea id="output" rows="10" style="width:100%" placeholder="Tu pojawi się tekst lub informacje o pliku..."></textarea>
<div style="margin-top:10px; display:flex; gap:10px; flex-wrap:wrap;">
<button id="saveTxt" type="button">Zapisz jako TXT</button>
<button id="saveBin" type="button">Zapisz jako BIN (z aktualnych danych)</button>
</div>
4) Odczyt pliku tekstowego (FileReader / text())
W przeglądarce najprościej odczytać tekst przez FileReader albo przez file.text(). Poniżej wersja z async/await i file.text() (czytelna i nowoczesna).
<script>
const form = document.getElementById("fileForm");
const fileInput = document.getElementById("fileInput");
const mode = document.getElementById("mode");
const output = document.getElementById("output");
const saveTxt = document.getElementById("saveTxt");
const saveBin = document.getElementById("saveBin");
// buffer na dane wejściowe (tekst albo ArrayBuffer)
let lastText = "";
let lastBuffer = null;
form.addEventListener("submit", async (e) => {
e.preventDefault();
try {
const file = fileInput.files?.[0];
if (!file) {
output.value = "Błąd: nie wybrano pliku.";
return;
}
output.value = `Nazwa: ${file.name}nRozmiar: ${file.size} BnTyp MIME: ${file.type || "(brak)"}nn`;
if (mode.value === "text") {
// ODCZYT TEKSTU
const text = await file.text();
lastText = text;
lastBuffer = null;
output.value += "=== PODGLĄD TEKSTU ===n" + text.slice(0, 5000);
if (text.length > 5000) output.value += "nn... (ucięto podgląd do 5000 znaków)";
} else {
// ODCZYT BINARNY
const buffer = await file.arrayBuffer();
lastBuffer = buffer;
lastText = "";
// pokażmy np. pierwsze 32 bajty w hex
const bytes = new Uint8Array(buffer);
const preview = Array.from(bytes.slice(0, 32))
.map(b => b.toString(16).padStart(2, "0"))
.join(" ");
output.value += "=== PODGLĄD BINARNY (pierwsze 32 bajty HEX) ===n" + preview;
}
} catch (err) {
// Obsługa błędów I/O
output.value = "Wystąpił błąd podczas odczytu pliku:n" + (err?.message || String(err));
}
});
// ZAPIS do pliku TXT (na podstawie output textarea lub lastText)
saveTxt.addEventListener("click", () => {
try {
const textToSave = output.value; // albo lastText, zależnie od celu
const blob = new Blob([textToSave], { type: "text/plain;charset=utf-8" });
downloadBlob(blob, "wynik.txt");
} catch (err) {
alert("Błąd zapisu TXT: " + (err?.message || String(err)));
}
});
// ZAPIS do pliku BIN (na podstawie lastBuffer; jeśli brak, spróbujemy zakodować tekst do UTF-8)
saveBin.addEventListener("click", () => {
try {
let blob;
if (lastBuffer) {
blob = new Blob([lastBuffer], { type: "application/octet-stream" });
} else {
// jeśli mamy tekst, zamieniamy go na bajty UTF-8
const encoder = new TextEncoder();
const bytes = encoder.encode(output.value);
blob = new Blob([bytes], { type: "application/octet-stream" });
}
downloadBlob(blob, "wynik.bin");
} catch (err) {
alert("Błąd zapisu BIN: " + (err?.message || String(err)));
}
});
// funkcja pomocnicza: pobranie pliku (download)
function downloadBlob(blob, filename) {
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
}
</script>
To jest najważniejszy „trik” zapisu w przeglądarce: tworzymy Blob, robimy z niego URL i klikamy link download. To nie zapisuje pliku „w tle” w dowolnym folderze – tylko daje użytkownikowi pobrać plik, a przeglądarka pyta gdzie go zapisać (lub zapisuje do domyślnego folderu pobrań).
5) Tekst vs binarka – co to znaczy?
Plik tekstowy
- to znaki (np. TXT, CSV, JSON),
- czytamy jako string:
file.text()lubreadAsText, - łatwo edytować w textarea.
Plik binarny
- to bajty (np. obraz, PDF, ZIP, dowolny BIN),
- czytamy jako
ArrayBuffer:file.arrayBuffer()lubreadAsArrayBuffer, - przetwarzamy przez
Uint8Arrayi inne typed arrays.
// ArrayBuffer -> bajty
const bytes = new Uint8Array(buffer);
// bajty -> tekst UTF-8
const decoder = new TextDecoder("utf-8");
const text = decoder.decode(bytes);
// tekst -> bajty UTF-8
const encoder = new TextEncoder();
const bytes2 = encoder.encode(text);
6) Obsługa wyjątków i błędów podczas operacji na plikach
W przeglądarce błędy podczas operacji na plikach to zwykle:
- brak pliku (użytkownik nic nie wybrał),
- niewłaściwy tryb odczytu (np. próbujesz traktować binarkę jako tekst),
- zbyt duży plik (warto ograniczyć rozmiar),
- odmowa uprawnień (gdy korzystasz z API wymagających zgody użytkownika),
- problem z kodowaniem znaków (np. plik w innym kodowaniu niż UTF-8).
Przykładowa walidacja: limit rozmiaru i dopuszczalne rozszerzenia:
function validateFile(file) {
const maxBytes = 2 * 1024 * 1024; // 2 MB
if (file.size > maxBytes) throw new Error("Plik jest za duży (max 2MB).");
const allowed = ["text/plain", "application/json", "text/csv", ""];
// file.type bywa puste w niektórych przypadkach – wtedy można sprawdzać rozszerzenie
if (file.type && !allowed.includes(file.type)) {
throw new Error("Niedozwolony typ pliku: " + file.type);
}
}
Najprostszy wzorzec obsługi błędów w I/O to try/catch wokół miejsca, gdzie czytasz lub zapisujesz. W UI warto wtedy wyświetlić komunikat użytkownikowi (np. w textarea lub w <div> z alertem).
7) Przykład praktyczny: wczytaj TXT, przetwórz, zapisz wynik
Załóżmy, że wczytujesz plik TXT (np. listę uczniów), a potem chcesz: usunąć puste linie, posortować i zapisać wynik do nowego pliku.
function processText(text) {
return text
.split(/r?n/)
.map(line => line.trim())
.filter(line => line.length > 0)
.sort((a, b) => a.localeCompare(b, "pl"));
}
// po odczycie:
const wynik = processText(lastText);
output.value = wynik.join("n");
8) Ważna uwaga: „prawdziwy zapis do pliku” bez pobierania
W „czystym JS” w przeglądarce standardowo zapisujesz plik poprzez pobranie (download). Istnieje też nowocześniejsze API pozwalające zapisać do pliku wskazanego przez użytkownika (okno „Zapisz jako”), ale nadal wymaga to interakcji użytkownika i nie działa wszędzie jednakowo. Jeśli chcesz trzymać się maksymalnej kompatybilności, metoda z Blob + download jest najpewniejsza.
