Įvaldykite Node.js failų operacijas su TypeScript. Šis vadovas apima sinchroninius, asinchroninius ir srautinius metodus, akcentuojant tipų saugumą.
TypeScript failų sistemos valdymas: Node.js failų operacijos su tipų saugumu pasauliniams programuotojams
Plačiame šiuolaikinės programinės įrangos kūrimo pasaulyje Node.js išsiskiria kaip galinga vykdymo aplinka, skirta kurti mastelio keitimui pritaikytas serverio programas, komandų eilutės įrankius ir kt. Esminis daugelio Node.js programų aspektas yra sąveika su failų sistema – failų ir katalogų skaitymas, rašymas, kūrimas ir valdymas. Nors JavaScript suteikia lankstumo atlikti šias operacijas, TypeScript įvedimas patobulina šią patirtį, įnešdamas statinį tipų tikrinimą, patobulintus įrankius ir galiausiai didesnį jūsų failų sistemos kodo patikimumą bei prižiūrimumą.
Šis išsamus vadovas skirtas pasaulinei programuotojų auditorijai, nepriklausomai nuo jų kultūrinės aplinkos ar geografinės padėties, siekiančiai įvaldyti Node.js failų operacijas su TypeScript teikiamu patikimumu. Mes gilinsimės į pagrindinį `fs` modulį, nagrinėsime jo įvairias sinchronines ir asinchronines paradigmas, išnagrinėsime modernias pažadais (promise) grįstas API ir atskleisime, kaip TypeScript tipų sistema gali ženkliai sumažinti įprastas klaidas ir pagerinti jūsų kodo aiškumą.
Pagrindinis elementas: Node.js failų sistemos (`fs`) supratimas
Node.js `fs` modulis suteikia API sąveikai su failų sistema, kuri modeliuojama pagal standartines POSIX funkcijas. Jis siūlo platų metodų spektrą, nuo paprastų failų skaitymo ir rašymo iki sudėtingų katalogų manipuliacijų ir failų stebėjimo. Tradiciškai šios operacijos buvo tvarkomos naudojant atgalinio iškvietimo funkcijas (callbacks), kas sudėtingais atvejais sukeldavo liūdnai pagarsėjusį „callback hell“. Tobulėjant Node.js, pažadai (promises) ir `async/await` tapo pageidaujamais asinchroninių operacijų modeliais, padarančiais kodą skaitomesnį ir lengviau valdomą.
Kodėl TypeScript failų sistemos operacijoms?
Nors Node.js `fs` modulis puikiai veikia su paprastu JavaScript, TypeScript integravimas suteikia keletą svarių pranašumų:
- Tipų saugumas: Sugaudomos įprastos klaidos, tokios kaip neteisingi argumentų tipai, trūkstami parametrai ar netikėtos grąžinamos reikšmės kompiliavimo metu, dar prieš paleidžiant kodą. Tai yra neįkainojama, ypač dirbant su įvairiomis failų koduotėmis, vėliavėlėmis ir `Buffer` objektais.
- Geresnis skaitomumas: Aiškios tipų anotacijos parodo, kokių duomenų funkcija tikisi ir ką ji grąžins, pagerindamos kodo supratimą įvairiose komandose.
- Geresni įrankiai ir automatinis užbaigimas: IDE (pvz., VS Code) naudoja TypeScript tipų apibrėžimus, kad suteiktų išmanų automatinį užbaigimą, parametrų užuominas ir integruotą dokumentaciją, ženkliai padidindamos produktyvumą.
- Pasitikėjimas refaktorinant: Kai pakeičiate sąsają ar funkcijos parašą, TypeScript iš karto pažymi visas paveiktas vietas, todėl didelio masto refaktorinimas tampa mažiau linkęs į klaidas.
- Globalus nuoseklumas: Užtikrina nuoseklų kodavimo stilių ir duomenų struktūrų supratimą tarptautinėse programuotojų komandose, mažindamas dviprasmiškumą.
Sinchroninės vs. asinchroninės operacijos: globali perspektyva
Suprasti skirtumą tarp sinchroninių ir asinchroninių operacijų yra labai svarbu, ypač kuriant programas, skirtas pasauliniam diegimui, kur našumas ir reaktyvumas yra prioritetai. Dauguma `fs` modulio funkcijų turi sinchronines ir asinchronines versijas. Paprastai tariant, asinchroniniai metodai yra pageidaujami neblokuojančioms I/O operacijoms, kurios yra būtinos norint išlaikyti jūsų Node.js serverio reaktyvumą.
- Asinchroninės (neblokuojančios): Šie metodai kaip paskutinį argumentą priima atgalinio iškvietimo funkciją arba grąžina `Promise`. Jos inicijuoja failų sistemos operaciją ir nedelsiant grįžta, leisdamos vykdyti kitą kodą. Kai operacija baigiama, iškviečiama atgalinio iškvietimo funkcija (arba `Promise` yra išsprendžiamas/atmetamas). Tai idealu serverio programoms, tvarkančioms kelias vienu metu vykstančias užklausas iš vartotojų visame pasaulyje, nes tai neleidžia serveriui „užšalti“ laukiant, kol bus baigta failo operacija.
- Sinchroninės (blokuojančios): Šie metodai visiškai atlieka operaciją prieš grąžindami rezultatą. Nors jas paprasčiau programuoti, jos blokuoja Node.js įvykių ciklą, neleidžiant vykdyti jokio kito kodo, kol nebus baigta failų sistemos operacija. Tai gali sukelti didelius našumo trūkumus ir nereaguojančias programas, ypač didelės apkrovos aplinkose. Naudokite jas saikingai, paprastai programos paleidimo logikai ar paprastiems scenarijams, kur blokavimas yra priimtinas.
Pagrindiniai failų operacijų tipai TypeScript
Pasinerkime į praktinį TypeScript taikymą su įprastomis failų sistemos operacijomis. Naudosime integruotus Node.js tipų apibrėžimus, kurie paprastai yra prieinami per `@types/node` paketą.
Norėdami pradėti, įsitikinkite, kad jūsų projekte įdiegti TypeScript ir Node.js tipai:
npm install typescript @types/node --save-dev
Jūsų `tsconfig.json` turėtų būti sukonfigūruotas atitinkamai, pavyzdžiui:
{
"compilerOptions": {
"target": "es2020",
"module": "commonjs",
"outDir": "./dist",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"]
}
Failų skaitymas: `readFile`, `readFileSync` ir Promises API
Turinio skaitymas iš failų yra pagrindinė operacija. TypeScript padeda užtikrinti, kad teisingai tvarkysite failų kelius, koduotes ir galimas klaidas.
Asinchroninis failo skaitymas (paremtas atgalinio iškvietimo funkcija)
`fs.readFile` funkcija yra pagrindinis įrankis asinchroniniam failų skaitymui. Ji priima kelią, pasirenkamą koduotę ir atgalinio iškvietimo funkciją. TypeScript užtikrina, kad atgalinio iškvietimo funkcijos argumentai yra teisingai tipizuoti (`Error | null`, `Buffer | string`).
import * as fs from 'fs';
const filePath: string = 'data/example.txt';
fs.readFile(filePath, 'utf8', (err: NodeJS.ErrnoException | null, data: string) => {
if (err) {
// Klaidos registravimas tarptautiniam derinimui, pvz., 'Failas nerastas'
console.error(`Klaida skaitant failą '${filePath}': ${err.message}`);
return;
}
// Failo turinio apdorojimas, užtikrinant, kad tai yra eilutė pagal 'utf8' koduotę
console.log(`Failo turinys (${filePath}):\n${data}`);
});
// Pavyzdys: dvejetainių duomenų skaitymas (koduotė nenurodyta)
const binaryFilePath: string = 'data/image.png';
fs.readFile(binaryFilePath, (err: NodeJS.ErrnoException | null, data: Buffer) => {
if (err) {
console.error(`Klaida skaitant dvejetainį failą '${binaryFilePath}': ${err.message}`);
return;
}
// 'data' čia yra buferis (Buffer), paruoštas tolesniam apdorojimui (pvz., siuntimui klientui srautu)
console.log(`Nuskaityta ${data.byteLength} baitų iš ${binaryFilePath}`);
});
Sinchroninis failo skaitymas
`fs.readFileSync` blokuoja įvykių ciklą. Jos grąžinamas tipas yra `Buffer` arba `string`, priklausomai nuo to, ar nurodyta koduotė. TypeScript teisingai tai nustato.
import * as fs from 'fs';
const syncFilePath: string = 'data/sync_example.txt';
try {
const content: string = fs.readFileSync(syncFilePath, 'utf8');
console.log(`Sinchroniškai nuskaitytas turinys (${syncFilePath}):\n${content}`);
} catch (error: any) {
console.error(`Sinchroninio skaitymo klaida '${syncFilePath}': ${error.message}`);
}
Pažadais (Promise) paremtas failo skaitymas (`fs/promises`)
Modernus `fs/promises` API siūlo švaresnę, pažadais paremtą sąsają, kuri yra labai rekomenduojama asinchroninėms operacijoms. TypeScript čia ypač pasitarnauja, ypač su `async/await`.
import * as fsPromises from 'fs/promises';
async function readTextFile(path: string): Promise
Failų rašymas: `writeFile`, `writeFileSync` ir vėliavėlės (Flags)
Duomenų rašymas į failus yra lygiai taip pat svarbus. TypeScript padeda valdyti failų kelius, duomenų tipus (eilutė arba buferis), koduotę ir failo atidarymo vėliavėles.
Asinchroninis failo rašymas
`fs.writeFile` naudojama duomenims rašyti į failą, pagal nutylėjimą pakeičiant failą, jei jis jau egzistuoja. Šį elgesį galite kontroliuoti su `flags`.
import * as fs from 'fs';
const outputFilePath: string = 'data/output.txt';
const fileContent: string = 'Tai naujas turinys, parašytas naudojant TypeScript.';
fs.writeFile(outputFilePath, fileContent, 'utf8', (err: NodeJS.ErrnoException | null) => {
if (err) {
console.error(`Klaida rašant į failą '${outputFilePath}': ${err.message}`);
return;
}
console.log(`Failas '${outputFilePath}' sėkmingai įrašytas.`);
});
// Pavyzdys su buferio (Buffer) duomenimis
const bufferContent: Buffer = Buffer.from('Dvejetainių duomenų pavyzdys');
const binaryOutputFilePath: string = 'data/binary_output.bin';
fs.writeFile(binaryOutputFilePath, bufferContent, (err: NodeJS.ErrnoException | null) => {
if (err) {
console.error(`Klaida rašant į dvejetainį failą '${binaryOutputFilePath}': ${err.message}`);
return;
}
console.log(`Dvejetainis failas '${binaryOutputFilePath}' sėkmingai įrašytas.`);
});
Sinchroninis failo rašymas
`fs.writeFileSync` blokuoja įvykių ciklą, kol rašymo operacija nebus baigta.
import * as fs from 'fs';
const syncOutputFilePath: string = 'data/sync_output.txt';
try {
fs.writeFileSync(syncOutputFilePath, 'Sinchroniškai įrašytas turinys.', 'utf8');
console.log(`Failas '${syncOutputFilePath}' įrašytas sinchroniškai.`);
} catch (error: any) {
console.error(`Sinchroninio rašymo klaida '${syncOutputFilePath}': ${error.message}`);
}
Pažadais (Promise) paremtas failo rašymas (`fs/promises`)
Modernus požiūris su `async/await` ir `fs/promises` dažnai yra švaresnis valdant asinchroninius rašymus.
import * as fsPromises from 'fs/promises';
import { constants as fsConstants } from 'fs'; // Vėliavėlėms
async function writeDataToFile(path: string, data: string | Buffer): Promise
Svarbios vėliavėlės:
- `'w'` (numatytoji): Atidaryti failą rašymui. Failas sukuriamas (jei neegzistuoja) arba jo turinys ištrinamas (jei egzistuoja).
- `'w+'`: Atidaryti failą skaitymui ir rašymui. Failas sukuriamas (jei neegzistuoja) arba jo turinys ištrinamas (jei egzistuoja).
- `'a'` (append): Atidaryti failą papildymui. Failas sukuriamas, jei neegzistuoja.
- `'a+'`: Atidaryti failą skaitymui ir papildymui. Failas sukuriamas, jei neegzistuoja.
- `'r'` (read): Atidaryti failą skaitymui. Išmetama išimtis, jei failas neegzistuoja.
- `'r+'`: Atidaryti failą skaitymui ir rašymui. Išmetama išimtis, jei failas neegzistuoja.
- `'wx'` (exclusive write): Kaip `'w'`, bet nepavyksta, jei kelias egzistuoja.
- `'ax'` (exclusive append): Kaip `'a'`, bet nepavyksta, jei kelias egzistuoja.
Papildymas į failus: `appendFile`, `appendFileSync`
Kai reikia pridėti duomenų į esamo failo pabaigą neperrašant jo turinio, `appendFile` yra jūsų pasirinkimas. Tai ypač naudinga žurnalų (logų) rašymui, duomenų rinkimui ar audito įrašams.
Asinchroninis papildymas
import * as fs from 'fs';
const logFilePath: string = 'data/app_logs.log';
function logMessage(message: string): void {
const timestamp: string = new Date().toISOString();
const logEntry: string = `${timestamp} - ${message}\n`;
fs.appendFile(logFilePath, logEntry, 'utf8', (err: NodeJS.ErrnoException | null) => {
if (err) {
console.error(`Klaida papildant žurnalo failą '${logFilePath}': ${err.message}`);
return;
}
console.log(`Pranešimas įrašytas į '${logFilePath}'.`);
});
}
logMessage('Vartotojas "Alice" prisijungė.');
setTimeout(() => logMessage('Sistemos atnaujinimas inicijuotas.'), 50);
logMessage('Duomenų bazės ryšys užmegztas.');
Sinchroninis papildymas
import * as fs from 'fs';
const syncLogFilePath: string = 'data/sync_app_logs.log';
function logMessageSync(message: string): void {
const timestamp: string = new Date().toISOString();
const logEntry: string = `${timestamp} - ${message}\n`;
try {
fs.appendFileSync(syncLogFilePath, logEntry, 'utf8');
console.log(`Pranešimas sinchroniškai įrašytas į '${syncLogFilePath}'.`);
} catch (error: any) {
console.error(`Sinchroninė klaida papildant žurnalo failą '${syncLogFilePath}': ${error.message}`);
}
}
logMessageSync('Programa paleista.');
logMessageSync('Konfigūracija įkelta.');
Pažadais (Promise) paremtas papildymas (`fs/promises`)
import * as fsPromises from 'fs/promises';
const promiseLogFilePath: string = 'data/promise_app_logs.log';
async function logMessagePromise(message: string): Promise
Failų trynimas: `unlink`, `unlinkSync`
Failų pašalinimas iš failų sistemos. TypeScript padeda užtikrinti, kad perduodate galiojantį kelią ir teisingai tvarkote klaidas.
Asinchroninis trynimas
import * as fs from 'fs';
const fileToDeletePath: string = 'data/temp_to_delete.txt';
// Pirmiausia sukurkime failą, kad užtikrintume, jog jis egzistuoja trynimo demonstracijai
fs.writeFile(fileToDeletePath, 'Laikinas turinys.', 'utf8', (err) => {
if (err) {
console.error('Klaida kuriant failą trynimo demonstracijai:', err);
return;
}
console.log(`Failas '${fileToDeletePath}' sukurtas trynimo demonstracijai.`);
fs.unlink(fileToDeletePath, (err: NodeJS.ErrnoException | null) => {
if (err) {
console.error(`Klaida trinant failą '${fileToDeletePath}': ${err.message}`);
return;
}
console.log(`Failas '${fileToDeletePath}' sėkmingai ištrintas.`);
});
});
Sinchroninis trynimas
import * as fs from 'fs';
const syncFileToDeletePath: string = 'data/sync_temp_to_delete.txt';
try {
fs.writeFileSync(syncFileToDeletePath, 'Sinchroninis laikinas turinys.', 'utf8');
console.log(`Failas '${syncFileToDeletePath}' sukurtas.`);
fs.unlinkSync(syncFileToDeletePath);
console.log(`Failas '${syncFileToDeletePath}' ištrintas sinchroniškai.`);
} catch (error: any) {
console.error(`Sinchroninio trynimo klaida '${syncFileToDeletePath}': ${error.message}`);
}
Pažadais (Promise) paremtas trynimas (`fs/promises`)
import * as fsPromises from 'fs/promises';
const promiseFileToDeletePath: string = 'data/promise_temp_to_delete.txt';
async function deleteFile(path: string): Promise
Failo egzistavimo ir leidimų tikrinimas: `existsSync`, `access`, `accessSync`
Prieš atliekant operaciją su failu, gali prireikti patikrinti, ar jis egzistuoja, arba ar dabartinis procesas turi reikiamus leidimus. TypeScript padeda, suteikdama tipus `mode` parametrui.
Sinchroninis egzistavimo tikrinimas
`fs.existsSync` yra paprastas, sinchroninis tikrinimas. Nors patogus, jis turi lenktynių sąlygos (race condition) pažeidžiamumą (failas gali būti ištrintas tarp `existsSync` ir vėlesnės operacijos), todėl svarbioms operacijoms dažnai geriau naudoti `fs.access`.
import * as fs from 'fs';
const checkFilePath: string = 'data/example.txt';
if (fs.existsSync(checkFilePath)) {
console.log(`Failas '${checkFilePath}' egzistuoja.`);
} else {
console.log(`Failas '${checkFilePath}' neegzistuoja.`);
}
Asinchroninis leidimų tikrinimas (`fs.access`)
`fs.access` tikrina vartotojo leidimus failui ar katalogui, nurodytam `path`. Ji yra asinchroninė ir priima `mode` argumentą (pvz., `fs.constants.F_OK` egzistavimui, `R_OK` skaitymui, `W_OK` rašymui, `X_OK` vykdymui).
import * as fs from 'fs';
import { constants } from 'fs';
const accessFilePath: string = 'data/example.txt';
fs.access(accessFilePath, constants.F_OK, (err: NodeJS.ErrnoException | null) => {
if (err) {
console.error(`Failas '${accessFilePath}' neegzistuoja arba prieiga draudžiama.`);
return;
}
console.log(`Failas '${accessFilePath}' egzistuoja.`);
});
fs.access(accessFilePath, constants.R_OK | constants.W_OK, (err: NodeJS.ErrnoException | null) => {
if (err) {
console.error(`Failas '${accessFilePath}' nėra skaitomas/rašomas arba prieiga draudžiama: ${err.message}`);
return;
}
console.log(`Failas '${accessFilePath}' yra skaitomas ir rašomas.`);
});
Pažadais (Promise) paremtas leidimų tikrinimas (`fs/promises`)
import * as fsPromises from 'fs/promises';
import { constants } from 'fs';
async function checkFilePermissions(path: string, mode: number): Promise
Failo informacijos gavimas: `stat`, `statSync`, `fs.Stats`
`fs.stat` funkcijų šeima suteikia išsamią informaciją apie failą ar katalogą, pvz., dydį, sukūrimo datą, modifikavimo datą ir leidimus. TypeScript `fs.Stats` sąsaja darbą su šiais duomenimis padaro labai struktūrizuotą ir patikimą.
Asinchroninis `stat`
import * as fs from 'fs';
import { Stats } from 'fs';
const statFilePath: string = 'data/example.txt';
fs.stat(statFilePath, (err: NodeJS.ErrnoException | null, stats: Stats) => {
if (err) {
console.error(`Klaida gaunant statistiką apie '${statFilePath}': ${err.message}`);
return;
}
console.log(`Statistika apie '${statFilePath}':`);
console.log(` Ar failas: ${stats.isFile()}`);
console.log(` Ar katalogas: ${stats.isDirectory()}`);
console.log(` Dydis: ${stats.size} baitų`);
console.log(` Sukūrimo laikas: ${stats.birthtime.toISOString()}`);
console.log(` Paskutinis modifikavimas: ${stats.mtime.toISOString()}`);
});
Pažadais (Promise) paremtas `stat` (`fs/promises`)
import * as fsPromises from 'fs/promises';
import { Stats } from 'fs'; // Vis tiek naudojame 'fs' modulio Stats sąsają
async function getFileStats(path: string): Promise
Katalogų operacijos su TypeScript
Katalogų valdymas yra įprastas reikalavimas organizuojant failus, kuriant programai skirtą saugyklą ar tvarkant laikinus duomenis. TypeScript suteikia patikimą tipizavimą šioms operacijoms.
Katalogų kūrimas: `mkdir`, `mkdirSync`
`fs.mkdir` funkcija naudojama naujiems katalogams kurti. `recursive` parinktis yra neįtikėtinai naudinga kuriant tėvinius katalogus, jei jie dar neegzistuoja, imituojant `mkdir -p` elgesį Unix tipo sistemose.
Asinchroninis katalogo kūrimas
import * as fs from 'fs';
const newDirPath: string = 'data/new_directory';
const recursiveDirPath: string = 'data/nested/path/to/create';
// Sukurti vieną katalogą
fs.mkdir(newDirPath, (err: NodeJS.ErrnoException | null) => {
if (err) {
// Ignoruoti EEXIST klaidą, jei katalogas jau egzistuoja
if (err.code === 'EEXIST') {
console.log(`Katalogas '${newDirPath}' jau egzistuoja.`);
} else {
console.error(`Klaida kuriant katalogą '${newDirPath}': ${err.message}`);
}
return;
}
console.log(`Katalogas '${newDirPath}' sėkmingai sukurtas.`);
});
// Sukurti įdėtus katalogus rekursyviai
fs.mkdir(recursiveDirPath, { recursive: true }, (err: NodeJS.ErrnoException | null) => {
if (err) {
if (err.code === 'EEXIST') {
console.log(`Katalogas '${recursiveDirPath}' jau egzistuoja.`);
} else {
console.error(`Klaida kuriant rekursyvų katalogą '${recursiveDirPath}': ${err.message}`);
}
return;
}
console.log(`Rekursyvūs katalogai '${recursiveDirPath}' sėkmingai sukurti.`);
});
Pažadais (Promise) paremtas katalogo kūrimas (`fs/promises`)
import * as fsPromises from 'fs/promises';
async function createDirectory(path: string, recursive: boolean = false): Promise
Katalogo turinio skaitymas: `readdir`, `readdirSync`, `fs.Dirent`
Norėdami gauti failų ir subkatalogų sąrašą tam tikrame kataloge, naudojate `fs.readdir`. `withFileTypes` parinktis yra modernus papildymas, kuris grąžina `fs.Dirent` objektus, suteikiančius išsamesnę informaciją tiesiogiai, nereikalaujant `stat` kiekvienam įrašui atskirai.
Asinchroninis katalogo skaitymas
import * as fs from 'fs';
const readDirPath: string = 'data';
fs.readdir(readDirPath, (err: NodeJS.ErrnoException | null, files: string[]) => {
if (err) {
console.error(`Klaida skaitant katalogą '${readDirPath}': ${err.message}`);
return;
}
console.log(`Katalogo '${readDirPath}' turinys:`);
files.forEach(file => {
console.log(` - ${file}`);
});
});
// Su `withFileTypes` parinktimi
fs.readdir(readDirPath, { withFileTypes: true }, (err: NodeJS.ErrnoException | null, dirents: fs.Dirent[]) => {
if (err) {
console.error(`Klaida skaitant katalogą su failų tipais '${readDirPath}': ${err.message}`);
return;
}
console.log(`Katalogo '${readDirPath}' turinys (su tipais):`);
dirents.forEach(dirent => {
const type: string = dirent.isFile() ? 'Failas' : dirent.isDirectory() ? 'Katalogas' : 'Kita';
console.log(` - ${dirent.name} (${type})`);
});
});
Pažadais (Promise) paremtas katalogo skaitymas (`fs/promises`)
import * as fsPromises from 'fs/promises';
import { Dirent } from 'fs'; // Vis tiek naudojame 'fs' modulio Dirent sąsają
async function listDirectoryContents(path: string): Promise
Katalogų trynimas: `rmdir` (pasenęs), `rm`, `rmSync`
Node.js tobulino savo katalogų trynimo metodus. `fs.rmdir` dabar didžiąja dalimi pakeistas `fs.rm` rekursyviam trynimui, siūlant patikimesnę ir nuoseklesnę API.
Asinchroninis katalogo trynimas (`fs.rm`)
`fs.rm` funkcija (prieinama nuo Node.js 14.14.0) yra rekomenduojamas būdas failams ir katalogams šalinti. `recursive: true` parinktis yra būtina norint ištrinti ne tuščius katalogus.
import * as fs from 'fs';
const dirToDeletePath: string = 'data/dir_to_delete';
const nestedDirToDeletePath: string = 'data/nested_dir/sub';
// Sąranka: Sukurti katalogą su failu viduje rekursyvaus trynimo demonstracijai
fs.mkdir(nestedDirToDeletePath, { recursive: true }, (err) => {
if (err && err.code !== 'EEXIST') {
console.error('Klaida kuriant įdėtą katalogą demonstracijai:', err);
return;
}
fs.writeFile(`${nestedDirToDeletePath}/file_inside.txt`, 'Kažkoks turinys', (err) => {
if (err) { console.error('Klaida kuriant failą įdėtame kataloge:', err); return; }
console.log(`Katalogas '${nestedDirToDeletePath}' ir failas sukurti trynimo demonstracijai.`);
fs.rm(nestedDirToDeletePath, { recursive: true, force: true }, (err: NodeJS.ErrnoException | null) => {
if (err) {
console.error(`Klaida trinant rekursyvų katalogą '${nestedDirToDeletePath}': ${err.message}`);
return;
}
console.log(`Rekursyvus katalogas '${nestedDirToDeletePath}' sėkmingai ištrintas.`);
});
});
});
// Tuščio katalogo trynimas
fs.mkdir(dirToDeletePath, (err) => {
if (err && err.code !== 'EEXIST') {
console.error('Klaida kuriant tuščią katalogą demonstracijai:', err);
return;
}
console.log(`Katalogas '${dirToDeletePath}' sukurtas trynimo demonstracijai.`);
fs.rm(dirToDeletePath, { recursive: false }, (err: NodeJS.ErrnoException | null) => {
if (err) {
console.error(`Klaida trinant tuščią katalogą '${dirToDeletePath}': ${err.message}`);
return;
}
console.log(`Tuščias katalogas '${dirToDeletePath}' sėkmingai ištrintas.`);
});
});
Pažadais (Promise) paremtas katalogo trynimas (`fs/promises`)
import * as fsPromises from 'fs/promises';
async function deleteDirectory(path: string, recursive: boolean = false): Promise
Pažangios failų sistemos koncepcijos su TypeScript
Be pagrindinių skaitymo/rašymo operacijų, Node.js siūlo galingas funkcijas, skirtas tvarkyti didesnius failus, nuolatinius duomenų srautus ir realaus laiko failų sistemos stebėjimą. TypeScript tipų deklaracijos puikiai išplečiamos šiems pažangiems scenarijams, užtikrindamos patikimumą.
Failų deskriptoriai ir srautai (Streams)
Labai dideliems failams arba kai reikalinga smulki failų prieigos kontrolė (pvz., konkrečios pozicijos faile), failų deskriptoriai ir srautai tampa būtini. Srautai suteikia efektyvų būdą tvarkyti didelius duomenų kiekius dalimis, vietoj viso failo įkėlimo į atmintį, kas yra gyvybiškai svarbu mastelio keitimui pritaikytoms programoms ir efektyviam išteklių valdymui serveriuose visame pasaulyje.
Failų atidarymas ir uždarymas su deskriptoriais (`fs.open`, `fs.close`)
Failo deskriptorius yra unikalus identifikatorius (skaičius), kurį operacinė sistema priskiria atidarytam failui. Galite naudoti `fs.open`, kad gautumėte failo deskriptorių, tada atlikti operacijas, tokias kaip `fs.read` ar `fs.write`, naudojant tą deskriptorių, ir galiausiai jį uždaryti su `fs.close`.
import * as fs from 'fs';
import { promises as fsPromises } from 'fs';
import { constants } from 'fs';
const descriptorFilePath: string = 'data/descriptor_example.txt';
async function demonstrateFileDescriptorOperations(): Promise
Failų srautai (`fs.createReadStream`, `fs.createWriteStream`)
Srautai yra galingi efektyviam didelių failų tvarkymui. `fs.createReadStream` ir `fs.createWriteStream` grąžina atitinkamai `Readable` ir `Writable` srautus, kurie sklandžiai integruojasi su Node.js srautų API. TypeScript suteikia puikius tipų apibrėžimus šiems srautų įvykiams (pvz., `'data'`, `'end'`, `'error'`).
import * as fs from 'fs';
const largeFilePath: string = 'data/large_file.txt';
const copiedFilePath: string = 'data/copied_file.txt';
// Sukurti dirbtinį didelį failą demonstracijai
function createLargeFile(path: string, sizeInMB: number): void {
const content: string = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. '; // 56 simboliai
const stream = fs.createWriteStream(path);
const totalChars = sizeInMB * 1024 * 1024; // Konvertuoti MB į baitus
const iterations = Math.ceil(totalChars / content.length);
for (let i = 0; i < iterations; i++) {
stream.write(content);
}
stream.end(() => console.log(`Sukurtas didelis failas '${path}' (${sizeInMB}MB).`));
}
// Demonstracijai, pirmiausia užtikriname, kad 'data' katalogas egzistuoja
fs.mkdir('data', { recursive: true }, (err) => {
if (err && err.code !== 'EEXIST') {
console.error('Klaida kuriant data katalogą:', err);
return;
}
createLargeFile(largeFilePath, 1); // Sukurti 1MB failą
});
// Kopijuoti failą naudojant srautus
function copyFileWithStreams(source: string, destination: string): void {
const readStream = fs.createReadStream(source);
const writeStream = fs.createWriteStream(destination);
readStream.on('open', () => console.log(`Skaitymo srautas failui '${source}' atidarytas.`));
writeStream.on('open', () => console.log(`Rašymo srautas failui '${destination}' atidarytas.`));
// Perduoti duomenis iš skaitymo srauto į rašymo srautą
readStream.pipe(writeStream);
readStream.on('error', (err: Error) => {
console.error(`Skaitymo srauto klaida: ${err.message}`);
});
writeStream.on('error', (err: Error) => {
console.error(`Rašymo srauto klaida: ${err.message}`);
});
writeStream.on('finish', () => {
console.log(`Failas '${source}' sėkmingai nukopijuotas į '${destination}' naudojant srautus.`);
// Išvalyti dirbtinį didelį failą po kopijavimo
fs.unlink(largeFilePath, (err) => {
if (err) console.error('Klaida trinant didelį failą:', err);
else console.log(`Didelis failas '${largeFilePath}' ištrintas.`);
});
});
}
// Palaukime šiek tiek, kol didelis failas bus sukurtas, prieš bandant jį kopijuoti
setTimeout(() => {
copyFileWithStreams(largeFilePath, copiedFilePath);
}, 1000);
Pokyčių stebėjimas: `fs.watch`, `fs.watchFile`
Failų sistemos pokyčių stebėjimas yra gyvybiškai svarbus užduotims, tokioms kaip greitas kūrimo serverių perkrovimas (hot-reloading), kūrimo procesai ar realaus laiko duomenų sinchronizavimas. Node.js tam siūlo du pagrindinius metodus: `fs.watch` ir `fs.watchFile`. TypeScript užtikrina, kad įvykių tipai ir klausytojų parametrai būtų teisingai tvarkomi.
`fs.watch`: Įvykiais paremtas failų sistemos stebėjimas
`fs.watch` paprastai yra efektyvesnis, nes dažnai naudoja operacinės sistemos lygio pranešimus (pvz., `inotify` Linux, `kqueue` macOS, `ReadDirectoryChangesW` Windows). Jis tinka stebėti konkrečius failus ar katalogus dėl pakeitimų, trynimų ar pervadinimų.
import * as fs from 'fs';
const watchedFilePath: string = 'data/watched_file.txt';
const watchedDirPath: string = 'data/watched_dir';
// Užtikrinti, kad failai/katalogai egzistuoja stebėjimui
fs.writeFileSync(watchedFilePath, 'Pradinis turinys.');
fs.mkdirSync(watchedDirPath, { recursive: true });
console.log(`Stebimas '${watchedFilePath}' dėl pokyčių...`);
const fileWatcher = fs.watch(watchedFilePath, (eventType: string, filename: string | Buffer | null) => {
const fname = typeof filename === 'string' ? filename : filename?.toString('utf8');
console.log(`Failo '${fname || 'N/A'}' įvykis: ${eventType}`);
if (eventType === 'change') {
console.log('Failo turinys galimai pasikeitė.');
}
// Realiame scenarijuje čia galėtumėte skaityti failą arba inicijuoti perkūrimą
});
console.log(`Stebimas katalogas '${watchedDirPath}' dėl pokyčių...`);
const dirWatcher = fs.watch(watchedDirPath, (eventType: string, filename: string | Buffer | null) => {
const fname = typeof filename === 'string' ? filename : filename?.toString('utf8');
console.log(`Katalogo '${watchedDirPath}' įvykis: ${eventType} faile '${fname || 'N/A'}'`);
});
fileWatcher.on('error', (err: Error) => console.error(`Failo stebėtojo klaida: ${err.message}`));
dirWatcher.on('error', (err: Error) => console.error(`Katalogo stebėtojo klaida: ${err.message}`));
// Simuliuoti pokyčius po pauzės
setTimeout(() => {
console.log('\n--- Simuliuojami pokyčiai ---');
fs.appendFileSync(watchedFilePath, '\nPridėta nauja eilutė.');
fs.writeFileSync(`${watchedDirPath}/new_file.txt`, 'Turinys.');
fs.unlinkSync(`${watchedDirPath}/new_file.txt`); // Taip pat išbandome trynimą
setTimeout(() => {
fileWatcher.close();
dirWatcher.close();
console.log('\nStebėtojai uždaryti.');
// Išvalyti laikinus failus/katalogus
fs.unlinkSync(watchedFilePath);
fs.rmSync(watchedDirPath, { recursive: true, force: true });
}, 2000);
}, 1000);
Pastaba apie `fs.watch`: Jis ne visada patikimas visose platformose visiems įvykių tipams (pvz., failų pervadinimai gali būti pranešami kaip trynimai ir kūrimai). Patikimam tarp platforminiam failų stebėjimui apsvarstykite bibliotekas, tokias kaip `chokidar`, kurios dažnai naudoja `fs.watch` viduje, bet prideda normalizavimo ir atsarginių mechanizmų.
`fs.watchFile`: Apklausos (polling) pagrindu veikiantis failų stebėjimas
`fs.watchFile` naudoja apklausą (periodiškai tikrina failo `stat` duomenis), kad aptiktų pokyčius. Jis yra mažiau efektyvus, bet nuoseklesnis skirtingose failų sistemose ir tinklo diskuose. Jis labiau tinka aplinkoms, kuriose `fs.watch` gali būti nepatikimas (pvz., NFS bendrinimai).
import * as fs from 'fs';
import { Stats } from 'fs';
const pollFilePath: string = 'data/polled_file.txt';
fs.writeFileSync(pollFilePath, 'Pradinis apklausos turinys.');
console.log(`Apklausiamas '${pollFilePath}' dėl pokyčių...`);
fs.watchFile(pollFilePath, { interval: 1000 }, (curr: Stats, prev: Stats) => {
// TypeScript užtikrina, kad 'curr' ir 'prev' yra fs.Stats objektai
if (curr.mtimeMs !== prev.mtimeMs) {
console.log(`Failas '${pollFilePath}' modifikuotas (pasikeitė mtime). Naujas dydis: ${curr.size} baitų.`);
}
});
setTimeout(() => {
console.log('\n--- Simuliuojamas apklausiamo failo pakeitimas ---');
fs.appendFileSync(pollFilePath, '\nPridėta dar viena eilutė į apklausiamą failą.');
setTimeout(() => {
fs.unwatchFile(pollFilePath);
console.log(`\nNustota stebėti '${pollFilePath}'.`);
fs.unlinkSync(pollFilePath);
}, 2000);
}, 1500);
Klaidų tvarkymas ir geriausios praktikos globaliame kontekste
Patikimas klaidų tvarkymas yra būtinas bet kuriai produkcijai paruoštai programai, ypač tai, kuri sąveikauja su failų sistema. Failų operacijos gali nepavykti dėl daugelio priežasčių: leidimų problemų, disko perpildymo klaidų, failo neradimo, I/O klaidų, tinklo problemų (tinklo prijungtiems diskams) ar vienalaikės prieigos konfliktų. TypeScript padeda sugauti su tipais susijusias problemas, tačiau vykdymo laiko klaidas vis tiek reikia kruopščiai valdyti.
Klaidų tvarkymo strategijos
- Sinchroninės operacijos: Visada apgaubkite `fs.xxxSync` iškvietimus `try...catch` blokais. Šie metodai išmeta klaidas tiesiogiai.
- Asinchroninės atgalinio iškvietimo funkcijos: Pirmasis argumentas `fs` atgalinio iškvietimo funkcijai visada yra `err: NodeJS.ErrnoException | null`. Visada pirmiausia patikrinkite šį `err` objektą.
- Pažadais paremta (`fs/promises`): Naudokite `try...catch` su `await` arba `.catch()` su `.then()` grandinėmis, kad tvarkytumėte atmetimus.
Naudinga standartizuoti klaidų registravimo formatus ir atsižvelgti į internacionalizaciją (i18n) klaidų pranešimams, jei jūsų programos klaidų grįžtamasis ryšys yra skirtas vartotojui.
import * as fs from 'fs';
import { promises as fsPromises } from 'fs';
import * as path from 'path';
const problematicPath = path.join('non_existent_dir', 'file.txt');
// Sinchroninis klaidų tvarkymas
try {
fs.readFileSync(problematicPath, 'utf8');
} catch (error: any) {
console.error(`Sinchroninė klaida: ${error.code} - ${error.message} (Kelias: ${problematicPath})`);
}
// Atgalinio iškvietimo funkcijos pagrindu veikiantis klaidų tvarkymas
fs.readFile(problematicPath, 'utf8', (err, data) => {
if (err) {
console.error(`Atgalinio iškvietimo klaida: ${err.code} - ${err.message} (Kelias: ${problematicPath})`);
return;
}
// ... apdoroti duomenis
});
// Pažadais paremtas klaidų tvarkymas
async function safeReadFile(filePath: string): Promise
Išteklių valdymas: failų deskriptorių uždarymas
Dirbant su `fs.open` (arba `fsPromises.open`), labai svarbu užtikrinti, kad failų deskriptoriai visada būtų uždaryti naudojant `fs.close` (arba `fileHandle.close()`) po operacijų pabaigos, net jei įvyksta klaidų. To nepadarius, gali atsirasti išteklių nutekėjimas, pasiekiama operacinės sistemos atidarytų failų riba ir potencialiai sugriūti jūsų programa arba paveikti kitus procesus.
`fs/promises` API su `FileHandle` objektais paprastai tai supaprastina, nes `fileHandle.close()` yra specialiai sukurtas šiam tikslui, o `FileHandle` egzemplioriai yra `Disposable` (jei naudojate Node.js 18.11.0+ ir TypeScript 5.2+).
Kelių valdymas ir suderinamumas tarp platformų
Failų keliai labai skiriasi tarp operacinių sistemų (pvz., `\` Windows, `/` Unix tipo sistemose). Node.js `path` modulis yra būtinas kuriant ir analizuojant failų kelius taip, kad jie būtų suderinami tarp platformų, o tai yra esminis dalykas globaliems diegimams.
- `path.join(...paths)`: Sujungia visus nurodytus kelio segmentus, normalizuodamas gautą kelią.
- `path.resolve(...paths)`: Išsprendžia kelių ar kelio segmentų seką į absoliutų kelią.
- `path.basename(path)`: Grąžina paskutinę kelio dalį.
- `path.dirname(path)`: Grąžina kelio katalogo pavadinimą.
- `path.extname(path)`: Grąžina kelio plėtinį.
TypeScript suteikia pilnus tipų apibrėžimus `path` moduliui, užtikrindama, kad jo funkcijas naudosite teisingai.
import * as path from 'path';
const dir = 'my_app_data';
const filename = 'config.json';
// Tarp platformų suderinamas kelio sujungimas
const fullPath: string = path.join(__dirname, dir, filename);
console.log(`Tarp platformų suderinamas kelias: ${fullPath}`);
// Gauti katalogo pavadinimą
const dirname: string = path.dirname(fullPath);
console.log(`Katalogo pavadinimas: ${dirname}`);
// Gauti pagrindinį failo pavadinimą
const basename: string = path.basename(fullPath);
console.log(`Pagrindinis pavadinimas: ${basename}`);
// Gauti failo plėtinį
const extname: string = path.extname(fullPath);
console.log(`Plėtinys: ${extname}`);
Konkurentiškumas ir lenktynių sąlygos (Race Conditions)
Kai vienu metu inicijuojamos kelios asinchroninės failų operacijos, ypač rašymai ar trynimai, gali kilti lenktynių sąlygos. Pavyzdžiui, jei viena operacija tikrina failo egzistavimą, o kita jį ištrina prieš pirmajai operacijai veikiant, pirmoji operacija gali netikėtai nepavykti.
- Venkite `fs.existsSync` kritinės kelio logikos; pirmenybę teikite `fs.access` arba tiesiog bandykite atlikti operaciją ir tvarkykite klaidą.
- Operacijoms, reikalaujančioms išskirtinės prieigos, naudokite atitinkamas `flag` parinktis (pvz., `'wx'` išskirtiniam rašymui).
- Įgyvendinkite užrakinimo mechanizmus (pvz., failų užraktai ar programos lygio užraktai) labai kritiškai bendrų išteklių prieigai, nors tai prideda sudėtingumo.
Leidimai (ACLs)
Failų sistemos leidimai (Prieigos kontrolės sąrašai arba standartiniai Unix leidimai) yra dažna klaidų priežastis. Užtikrinkite, kad jūsų Node.js procesas turi reikiamus leidimus skaityti, rašyti ar vykdyti failus ir katalogus. Tai ypač aktualu konteinerizuotose aplinkose ar daugelio vartotojų sistemose, kur procesai veikia su konkrečiomis vartotojų paskyromis.
Išvada: tipų saugumo priėmimas globalioms failų sistemos operacijoms
Node.js `fs` modulis yra galingas ir universalus įrankis sąveikai su failų sistema, siūlantis spektrą galimybių nuo pagrindinių failų manipuliacijų iki pažangaus srautais paremto duomenų apdorojimo. Pridėdami TypeScript ant šių operacijų, jūs gaunate neįkainojamų privalumų: kompiliavimo laiko klaidų aptikimą, geresnį kodo aiškumą, aukštesnės kokybės įrankių palaikymą ir didesnį pasitikėjimą refaktorinimo metu. Tai ypač svarbu globalioms programuotojų komandoms, kuriose nuoseklumas ir sumažintas dviprasmiškumas įvairiose kodo bazėse yra gyvybiškai svarbūs.
Nesvarbu, ar kuriate mažą pagalbinį scenarijų, ar didelio masto verslo programą, TypeScript patikimos tipų sistemos panaudojimas jūsų Node.js failų operacijoms lems lengviau prižiūrimą, patikimesnį ir atsparesnį klaidoms kodą. Priimkite `fs/promises` API švaresniems asinchroniniams modeliams, supraskite niuansus tarp sinchroninių ir asinchroninių iškvietimų ir visada teikite pirmenybę patikimam klaidų tvarkymui bei tarp platformų suderinamam kelių valdymui.
Taikydami šiame vadove aptartus principus ir pavyzdžius, programuotojai visame pasaulyje gali kurti failų sistemos sąveikas, kurios yra ne tik našios ir efektyvios, bet ir iš esmės saugesnės bei lengviau suprantamos, galiausiai prisidedant prie aukštesnės kokybės programinės įrangos produktų.