Originally planned to write the algorithm course set, this semester course is too heavy, really can not update the technology blog, but still feel ashamed, accidentally saw the denO group someone talked about Koa, Nest. Js, Express comparison, can not help to look at Koa source, wrote a hydrology. The author is not good enough (actually I haven’t used Koa, I just went to see the Api on the official website), welcome to make mistakes, then I went to write the course design ~

First of all, the source version I refer to is the first release of Koa (0.0.2). Js, context.js, and status.js, which are discussed in order.

Context

Let’s look at the Context first.

The Koa Context encapsulates node’s request and response objects into a single object, providing many useful methods for writing Web applications and apis. These operations are frequently used in HTTP server development, and being added to this level rather than higher level frameworks forces middleware to re-implement this common functionality.

Module dependencies

var debug = require('debug') ('koa:context');
var Negotiator = require('negotiator');
var statuses = require('./status');
var qs = require('querystring');
var Stream = require('stream');
var fresh = require('fresh');
var http = require('http');
var path = require('path');
var mime = require('mime');
var basename = path.basename;
var extname = path.extname;
var url = require('url');
var parse = url.parse;
var stringify = url.format;
Copy the code

The Context section has the most code, but very little to say. In this section, Koa encapsulates many of the methods commonly used in HTTP modules into a single object using the getter/setter accessor property, passing in app.context(), which will be mentioned later.

Application

Application is the entry file for Koa.

Module dependencies

var debug = require('debug') ('koa:application');
var Emitter = require('events').EventEmitter;
var compose = require('koa-compose');
var context = require('./context');
var Cookies = require('cookies');
var Stream = require('stream');
var http = require('http');
var co = require('co');
Copy the code

Interesting constructor

The first line of the constructor is interesting:

if (! (this instanceof Application)) return new Application;

The purpose of this line is to prevent users from forgetting to use the new keyword. In the days before the class keyword was introduced into JS, constructors were a semantic pitfall because they could be called “normally” as normal functions.

var app = Application.prototype;
exports = module.exports = Application;
function Application() {
  if(! (this instanceof Application)) return new Application;
  this.env = process.env.NODE_ENV || 'development';
  this.on('error'.this.onerror);
  this.outputErrors = 'test'! =this.env;
  this.subdomainOffset = 2;
  this.poweredBy = true;
  this.jsonSpaces = 2;
  this.middleware = [];
  this.Context = createContext();
  this.context(context);
}
Copy the code

The Application constructor prototype contains the following methods:

  • listen()

    app.listen = function(){
      var server = http.createServer(this.callback());
      return server.listen.apply(server, arguments);
    };
    Copy the code

    As you can see, this is nothing special about using Node’s HTTP module to create an HTTP service.

  • use()

    app.use = function(fn){
      debug('use %s', fn.name || The '-');
      this.middleware.push(fn);
      return this;
    };
    Copy the code

    A Koa application is an object containing a set of middleware functions that are organized and executed in a stack-like fashion.

    This function is used to add middleware methods to the application. Note that app.use() returns this, so it can be chained, and this is also stated in the official documentation.

    That is:

    app.use(someMiddleware)
      .use(someOtherMiddleware)
      .listen(3000)
    Copy the code
  • context()

    app.context = function(obj){
      var ctx = this.Context.prototype;
      var names = Object.getOwnPropertyNames(obj);
    
      debug('context: %j', names);
      names.forEach(function(name){
        if (Object.getOwnPropertyDescriptor(ctx, name)) {
          debug('context: overwriting %j', name);
        }
        var descriptor = Object.getOwnPropertyDescriptor(obj, name);
        Object.defineProperty(ctx, name, descriptor);
      });
    
      return this;
    };
    Copy the code

    The context here actually gives the user a way to add other properties (DIY) to the context as well.

  • callback()

    app.callback = function(){
      var mw = [respond].concat(this.middleware);
      var gen = compose(mw);
      var self = this;
    
      return function(req, res){
        var ctx = new self.Context(self, req, res);
    
        co.call(ctx, gen)(function(err){
          if(err) ctx.onerror(err); }); }};Copy the code

    First let’s look at the second line of code:

    var mw = [respond].concat(this.middleware);

    What is this?

    There is also a generator function respond in the applation. Js file, which is a Response middleware. Therefore, this line of code simply makes Response Middleware the first element in middleware.

    And then we’re going to focus on compose, which comes from KoA-compose, which I think is the essence of KOA, and the code is pretty neat.

    function c (middleware) {
      if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array! ')
      for (const fn of middleware) {
        if (typeoffn ! = ='function') throw new TypeError('Middleware must be composed of functions! ')}return function (context, next) {
        let index = -1
        return dispatch(0)
        function dispatch (i) {
          if (i <= index) return Promise.reject(new Error('next() called multiple times'))
          index = i
          let fn = middleware[i]
          if (i === middleware.length) fn = next
          if(! fn)return Promise.resolve()
          try {
            return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
          } catch (err) {
            return Promise.reject(err)
          }
        }
      }
    }
    Copy the code

    Some people say that koa-compose originated in functional programming. I agree that koa-compose is a bit like compose in FP, but there is a big difference. For example, for compose, the functions that come in for compose run from right to left, emphasizing that data flows through a pipeline through a combination of functions. Make our code simpler and more readable.

    Koa-compose’s main purpose here is to continuously issue execution rights in the middleware stack, converting asynchronous calls to synchronous calls.

    Let’s take an example:

    let one = (ctx, next) = > {
      console.log('middleware one execute begin');
      next();
      console.log('middleware one execute end');
    }
    
    let two = (ctx, next) = > {
      console.log('middleware two execute begin');
      next();
      console.log('middleware two execute after');
    }
    
    let three = (ctx, next) = > {
      console.log('middleware three execute begin');
      next();
      console.log('middleware three execute after');
    }
    
    compose([one,two,three])
    Copy the code

    The final print looks like this:

    middleware one execute begin
    middleware two execute begin
    middleware three execute begin
    middleware three execute after
    middleware two execute after
    middleware one execute end
    Copy the code

    Why is that?

    If you look closely at the compose function, it does a type check for middleware itself and its elements, and then uses a closure to hold the index subscript. The key is the following line:

    Promise.resolve(fn(context, dispatch.bind(null, i + 1)));

    Fn is a middleware function, and the second argument passed in is next, which is the next middleware function: Middleware [I +1]. Next is called in the current middleware function to pass execution to the next middleware function.

    Note: KoA-compose is a new version of KOA, which is composed using generators. The idea is the same, but the implementation is different. So you can see that the co function, which references a library called CO, is used for automatic execution of Generator functions so that you don’t have to write your own actuators.

  • onerror()

    app.onerror = function(err){
      if (!this.outputErrors) return;
      if (404 == err.status) return;
      console.error(err.stack);
    };
    Copy the code

    Well, there’s nothing to say. Y1s1 is pretty crude. We would certainly like the listener bindings to be richer. But the first edition, I understand.

Status

There are 17 lines with comments.

var http = require('http');
var codes = http.STATUS_CODES;

/** * Produce exports[STATUS] = CODE map. */

Object.keys(codes).forEach(function(code){
  var n = ~~code;
  var s = codes[n].toLowerCase();
  exports[s] = n;
});
Copy the code

In effect, the HTTP module’s STATUS_CODES key-value relation is inverted, and then string -> number is double-fetched with the ~~ antilogic operator. As for why to use such a hack method, I can’t guess.

The End

Finally, I think Koa differs from a Web framework like Express in that Koa is more like a shelf, providing only a mechanism for middleware registration and invocation. This makes Koa lighter and more freedom, and Koa uses generator and CO for elegant interception of HTTP requests and responses. After version 2.0, Koa replaced generator with async and no longer needs co, but this is just tweaks to the language specification. Async and await are just syntactic sugar of generator.

So that’s it.

reference

  • Koa website
  • Functional programming refers to north -compose
  • Co function library