Node.JS 6: Asynchronní a synchronní operace se soubory

V minulé epizodě jsme se podívali na základní funkčnost fs modulu, dnes se ponoříme do hlubšího porozumění rozdílů mezi asynchronními a synchronními metodami, které zpracovávají soubory. Asynchronní a synchronní přístup bývá často označován i jako blokující (blocking) a neblokující (non-blocking) kód.

Blokující (blocking) a neblokující (non-blocking) způsob vykonávání kódu

V případě blokujícího kódu dochází ke spuštění Javascriptu až poté, co jsou hotové nejavascriptové (non-Javascript) operace. Děje se to díky event loop (událostní smyčce), která není schopná pokračovat v Javascriptovém kódu a zároveň vykonávat blokující (blocking) operaci. Nejčastěji stojí za blokujícími operacemi V/V (angl. I/O) metody.

Princip V/V (I/O) - I/O představuje zkratku pro Input/Output, česky Vstup/Výstup. V/V (I/O) systém přenáší informace mezi hlavní pamětí počítače a vnějším světem. , sám se sestává z V/V (I/O) zařízení (periferních - např. myš, klávesnice nebo webkamera), V/V (I/O)řadičem (CU - Central unit, tj. elektronickou řídící jednotkou, která vede činnost všech částí počítače a je součástí procesoru (CPU)) a V/V (I/O) softwaru, který vykoná V/V (I/O) transakci pomocí sekvence V/V (I/O) operací - a obsahuje i již zmíněné V/V (I/O) metody.

Blokující metody vykonávají kód synchronně, neblokující asynchronně.

Rozdíly v běžném kódu

Jaké odlišnosti pro nás představuje toto rozdělení v samotném skriptu? Uveďme si příklad již známé metody fs.readFile():

Synchronní verze

const fs = require('fs');

fs.writeFileSync('soubor.txt', 'Dobrý den!');
if (fs.existsSync('soubor.txt')) {
    console.log("Uloženo!")
}

const data = fs.readFileSync('soubor.txt');
console.log(data.toString())

function dalsiUloha() {
console.log("Další úloha.");
}
dalsiUloha();

Výpis v konzoli:

Uloženo!
Dobrý den!
Další úloha.

Abychom zjistili, zda-li je již vytvořen soubor.txt, použijeme další synchronní funkci fs.existsSync (pokud soubor existuje, vykoná se podmínka - v našem případě se vypíše Uloženo!, ovšem jestliže se soubor metodou fs.readFileSync ještě nevytvořil, příkaz Uloženo! se nevypíše.

Asynchronní verze

const fs = require('fs');

fs.writeFile('soubor.txt', 'Dobrý den!', function(err){
    if (err) throw err;
    console.log('Uloženo!');
});

fs.readFile('soubor.txt', function (err, data) {
if (err) throw err;
console.log(data.toString())
});

function dalsiUloha() {
console.log("Další úloha.");
}
dalsiUloha();

Výpis v konzoli:

Další úloha.
Uloženo!
Dobrý den!

V prvním případě voláme console.log před dalsiUloha(), v druhém případě (neblokujícím) může vykonávání javascriptového kódu pokračovat tak, že dalsiUloha() se začne vykonávat jako první. Schopnost spustit další úlohy aniž bychom museli čekat na přečtení souboru k dokončení prvního úkolu zadání patří mezi klíčové kroky umožňující vyšší průtok informací rychleji.

Kombinování blokujícího a neblokujícího kódu občas nese neblahé následky - např. pokud se rozhodneme přečíst soubor asynchronně a následně jej synchronně odstranit, může dojít nejprve k jeho vymazání a potom k pokusu jej přečíst:

const fs = require('fs');
fs.readFile('soubor.txt', function (err, data){
    if (err) throw err;
    console.log(data.toString());
});
fs.unlinkSync('soubor.txt');

Pokud bude soubor.txt prázdný, fs.readFile jej přečte velmi rychle a k chybovému hlášení nedojde.

Zcela asynchronní verze by mohla vypadat například takto:

const fs = require('fs');
fs.readFile('soubor.txt', function (err, data){
    if (err) throw err;
    console.log(data.toString());
    fs.unlink('soubor.txt', function (err, data) {
        if (err) throw err;
    });
});

Tento kód využívá vnoření fs.unlink do callback funkce ( = funkce volající další funkci) fs.readFile, díky čemuž máme garantované správné pořadí operací.