Jdeme budoucnosti naproti s Koa.js - startujeme nový projekt

Budoucnost asynchronního programování v Node.js je vytyčena a chceme-li se jí přiblížit, musíme sáhnout po jednom z modulů podporujícím asynchronní generátory. A právě Koa.js je modul s rodokmenem.

Proč potřebujeme na webserveru generátory?

Pokud nevíte o čem je řeč, podívejte se nejprve co jsou generátory v JavaScriptu a k čemu jsou dobré.

Jako webový vývojář určitě tušíte, že v běžných webových aplikacích je potřeba pro každou odpověď klientovi provést nějaký ten dotaz do databáze či do cache. A v naprosté většině případů každý další dotaz nějak využívá informace z předchozích dotazů - vzniká tak sekvence asynchronních operací. S pomocí Promises by kód vypadal takto:

app.use((req, res, next) => {  
    let fooRes;
    foo.find({ _id: req.query.id })
        .next()
        .then((fooObj) => {
            fooRes = fooObj;
            return bar.find({ _id: fooObj.barId })
                .next()
        })
        .then((barObj) => {
            fooRes.bar = barObj;
            res.send(fooRes);
        })
        .catch((e) => {
            next(e);
        });
});

Jak by totéž fungovalo s použitím generátorů? Rozdíl v čitelnosti kódu je totální:

app.use(function *() {  
    let fooRes = yield foo.find({ _id: this.request.query.id })
        .next();

    let barRes = yield bar.find({ _id: fooRes.barId })
        .next();

    fooRes.bar = barRes;
    this.request.body = fooRes;
});

Proč zrovna Koa.js? Co to vlastně je?

Koa je minimální webový modul od tvůrců Express.js, který od začátku řeší nepříjemný problém s asynchronními operacemi, tím že implementuje generátory. Ale proč se odvracet od zavedeného Expressu kvůli jedné feature, kterou řeší co-wrapper pro Express? Odpověď je prostá:

  1. Udržujme stack aplikace co nejmenší. Každý middleware či wrapper navíc se negativně projevuje na výkonu aplikace. Čím jednodušší řešení, tím lépe.
  2. Bavíme se jen o webovém modulu. Koa není od Expressu koncepčně odlišná a nijak za ním nepokulhává. Je jeho další generací.
  3. Každá feature navíc, kterou nevyužijeme, brzdí aplikaci a Koa je ještě jednodušší a modulárnější než Express.

Začínáme

Podíváme se, jak vytvořit s pomocí Koa.js stejnou aplikaci, jako z příkladu s Express.js. Pokud jste v Node.js ještě nováčkem, doporučuji si příklad prohlédnout, abyste se zorientovali v základní struktuře projektu. Nejprve nainstalujeme všechny potřebné moduly včetne routeru, který na rozdíl od Expresu není součástí základního modulu:

npm install --save koa koa-hbs koa-router  

app.js - základní kámen aplikace

V bootstrapu zapojujeme a konfigurujeme jednotlivé moduly aplikace. Šablonovací systém, kterým je v našem případě Handlebars, potřebuje pouze vědět, kde najde své šablony. Router stačí zapojit pouze pomocí metody .routes(). Vynechávám ošetření chybových stavů, kromě vykreslení chybové šablony. Ta skvěle ilustruje princip fungování Koa.js a jeho výhodu oproti Expressu:

const Koa = require('koa');  
const path = require('path');  
const koaHbs = require('koa-hbs');

const routes = require('./routes');

const app = new Koa();

// attach templating engine
app.use(koaHbs.middleware({  
    defaultLayout: 'default',
    layoutsPath: path.join(viewPath, 'layouts'),
    viewPath: path.join(__dirname, 'views'),
    extname: '.hbs'
}));

// let's override our error handler
app.use(function *(next) {  
    try {
        yield next;
    } catch (err) {
        this.res.status = err.code || 500;
        yield this.render('error');
        this.app.emit('error', err, this);
    }
});

// mount a main router
app.use(routes.routes());

module.exports = app;  

Na rozdíl od Expressu, middleware funkce dostává pouze jedinný parametr, kterým je Promise následujícího middlewaru: next. Jeho yieldem proběhnou všechny následující middlewary, díky tomu jde napřiklad měřit a zaznamenávat rychlost odpovědi, ale hlavně je možné všechny chyby z těchto middlewarů odchytit pomocí konstrukce try/catch - což je užitečná vlastnost generátorů.

routes/index.js - základ routeru

Kam ale zmizel request a response? V Koa.js jsou součástí kontextu (this). K odpovědi ani response nepotřebujeme, protože nám Handlebars middleware do kontextu přidal metodu render(). Té stačí pouze předat název šablony k jejímu volání nesmíme zapomenout přidat slůvko yield.

// create a router instance
const KoaRouter = require('koa-router');

const router = new KoaRouter();

// homepage
router.get('/', function *() {  
    // render index.hbs template
    yield this.render('index');
});

module.exports = router;  

bin/www - spouštíme

Ke spouštění webserveru potřebujeme Node.js modul http, kterému předáme callback z instance Koa.js aplikace (získáme ho metodou callback()). Tento skript pak zbývá jen spustit a je hotovo. V prohlížeči na adrese http://localhost:3000 uvidíte obsah šablony views/index.hbs.

const app = require('../app');  
const http = require('http');

const server = http.createServer(app.callback());

// fetch port from environment variable
const port = process.env.PORT || 3000;

// start server
server.listen(port);  

V příkladu samozřejmě chybí celá řada nezbytných ošetření různých nestandardních stavů (404, chyby webserveru, a.t.d.). A pokud vás zajímá, jak výsledný projekt vypadá:

Nic netrvá věčně a už dnes je venku druhá verze Koa.js, která staví na konstrukcích asynchronních funkcí async/await. V současné verzi Node.js (5.x) však asynchronní funkce podporovány nejsou a pravděpodobně si na ně budeme muset ještě chvíli počkat, pokud nechcete používat transpiler i na serverový kód. I tak je Koa skvělým směrem, kterým stojí zato se vydat.

David Menger

Read more posts by this author.