📜 ⬆️ ⬇️

Meet Koa or coroutine in nodejs

Foreword


I have been attracted to javascript for a very long time as a single language for web development, but until recently all my research ended with reading nodejs documentation and articles that this is a callback hell, that developing it brings only pain and suffering. I have not yet discovered that the yield operator appeared in harmony, after which I came across koa, and it went off.

What's the salt


Actually, koa is in many ways similar to its predecessor - express, with the exception of the omnipresent callbacks. Instead, he uses coroutines in which control can be transferred to subroutines (thunk), promises (promises), an array with subroutines \ promises, or an object. In some places, some backward compatibility has been preserved through a group of co functions written by the creators of koa and many followers. Any function that previously used callback can be thunkify for use with co or koa.

It looks like this:

var func = function(opt){ return function(done){ /* … */ (…) && done(null, data) || done(err) } } var func2 = function(opt){ return function(done){ oldFuncWithCallback(opt, function(err, data){ (...) && done(null, data) || done(err) } } } co(function*{ /* … */ var result = yield func(opt); var result2 = yield func2(opt); var thunkify = require('thunkify'); var result3 = yield thunkify(oldFuncWithCallback(opt)) })() 

')
In this case, data will return to result, and done (err) will cause an exception, and you will not leave the function, as it would have been with callback, and you will not block the interpreter, execution will go to the next yield, and it looks elegant, in other words - just a fairy tale.

Time to write code

Koa is based on middleware, as well as express, but now they are executed as a coroutine, like tornado in python. Further the code of a simple site and my thoughts goes.

Project structure:


Since my previous hobby was Django, it may seem to someone that this has affected the organization of the code in the project, maybe it's true, I like to organize the code into modules.

src / server / index.js

 'use strict'; var koa = require('koa'); var path = require('path'); var compose = require('koa-compose'); //      middleware`   var app = module.exports = koa(); //   var projectRoot = __dirname; var staticRoot = path.join(projectRoot, '../public'); var templateRoot = path.join(projectRoot, '../template'); //      settings.py  django var middlewareStack = [ require('koa-session')(), //    session require('koa-less')('/less', {dest: '/css', pathRoot: staticRoot}), //  less  css,      ,     require('koa-logger')(), //   http  require('koa-favicon')(staticRoot + '/favicon.png'), require('koa-static')(staticRoot), //  ,   ,     nginx` require('koa-views')(templateRoot, {'default': 'jade'}) // Jade      nodejs ]; require('koa-locals')(app); //   locals   ,      ,   app.use(compose(middlewareStack)); /*   middleware    ,       -   ,   callback`       ,   , ,    */ var routes = require('./handlers'); app.use(function *(next) { //   this, middleware  app,        this.locals.url = function (url, params) { return routes.url(url, params); }; yield next }); /*    middleware,        url,     ,    ,         */ app.use(routes.middleware()); 

It must be remembered that this chain is called every time the server receives a request. To better understand the order of their execution, you can use the following example:

 app.use(function*(next){ console.log(1) yield heavyFunc() console.log(2) yield next }) app.use(function*(next){ console.log(3) yield next }) 

Each request to the console will be displayed.
  one
 3
 2 

Next, in the server folder, I put handlers.js, a module that registers applications from the src / app folder.

src / server / handlers.js

 var Router = require('koa-router'); var router = new Router(); function loadRoutes(obj, routes){ routes.forEach(function(val){ var func = val.method.toLowerCase() == 'get' ? obj.get : val.method.toLowerCase() == 'post' ? obj.post : val.method.toLowerCase() == 'all' ? obj.all : obj.get; return func.call(obj, val.name, val.url, val.middleware) }) } loadRoutes(router, require('src/app/home').routes); //      app module.exports = router; 


The module encapsulates the loadRoutes method, which accepts a newly created instance of the router and a list of objects containing route information. Using home as an example, I’ll show how applications for working with this module look like:

src / app / home.js
 function* index(next){ yield this.render('home/index', { Hello: 'World!' }) } var routes = [ {method: 'get', name: 'index', url: '/', middleware: index} ]; exports.routes = routes; 


It looks very simple and organic, here I went a little further than the modularity proposed in django, I liked the complete isolation of the module from the rest of the application, including my own routes. Of course, with this approach, a conflict of urls may arise and you will not get what you expected. You can add the name of the application, or use koa-mount, or improve the recorder to prevent duplicates.

I must say that for rendering the page you need to fill this.body, which is what this.render does, or transfer the execution further, with the help of yield next, otherwise you will get “Not Found” in the body of the page. If none of the middlewares have filled the body and continued execution, the correct 404 page can be drawn by placing the following middleware at the end of src / server / index.js:

 app.use(function*(){ this.status = 404; yield this.render('service/404') //  ,    }) 


Conclusion


For sweet decided to leave error handling. From the adepts of nodejs and express, I heard that this does not require hefty attention to each callback and even it does not always help. If we recall the order in which the middleware is executed, global processing can be done simply by adding the following code to the beginning of the request processing:

 app.use(function* (next){ try { yield next } catch (err) { this.app.emit('error', err, this); //      yield this.render('service/error', { message: err.message, error: err }) } ) 


With this we conclude all the project code in try..catch, well, do not forget that the app is primarily an eventEmitter. In my opinion, this is simply brilliant. There are already a lot of modules written for koa, almost every module for express is already adapted for koa, such as mongoose, passport, request and many others. So we got asynchronous programming, which brings joy and fun. In addition, the notorious TJ remains to support koa.

Philosophically, Koa seeks to fix and replace nodejs, while Express extends nodejs.

Excerpt from the beginning of the article, koa-vs-express .

Thanks for reading. Best regards, all nodejs.

Source: https://habr.com/ru/post/238095/


All Articles