What is the koa

Koa 2 does:

  1. Enable request and Response objects based on node’s native REq and RES and encapsulate them into a context object.

  2. Middleware Onion model mechanism based on async/await.

The main source difference between KOA1 and KOA2 is the way asynchronous middleware is supported.

Koa1 uses generator, yield).

Koa2 uses the async/await+Promise pattern. The following is mainly for koA2 version of the source code.

First read koA source code

Koa source code is actually very simple, a total of four files.

─ ─ lib ├ ─ ─ application. Js ├ ─ ─ the context, js ├ ─ ─ request. Js └ ─ ─ response. JsCopy the code

These four files actually correspond to the four objects of KOA:

─ ─ lib ├ ─ ─newKoa () | | CTX. App ├ ─ ─ CTX ├ ─ ─ CTX. The req | | CTX. Request └ ─ ─ CTX. Res | | CTX. The responseCopy the code

Request. js and Response. js are logically identical in that they both expose an object, and the object properties are read, written and controlled through getter and setter.

Below, we have a preliminary understanding of koA source content, read them, you can have a preliminary understanding of KOA.

application.js

Application.js is the entry file to koA (as you can see from the main field (lib/application.js) of package.json in the KOA folder) and is the core part of it.

/** * Dependency modules, including but not limited to the following, only list the core needs to be concerned with */
const response = require('./response');
const compose = require('koa-compose');
const context = require('./context');
const request = require('./request');
const Emitter = require('events');
const convert = require('koa-convert');
/** * Application can process asynchronous events */
module.exports = class Application extends Emitter {
 constructor() {
 super(a);this.middleware = []; // This array holds all middleware functions introduced through the use function
 this.subdomainOffset = 2; // Number of domain names to ignore
 this.env = process.env.NODE_ENV || 'development';
 // Use context.js, request.js, response.js to create the corresponding context, request, and response. Why Object. Create will be explained below
 this.context = Object.create(context);
 this.request = Object.create(request);
 this.response = Object.create(response);
 }
 // Create a serverlisten(... args) { debug('listen');
 const server = http.createServer(this.callback()); //this.callback() is the important part, which corresponds to the http.createserver parameter (req, res)=> {}
 returnserver.listen(... args); }/* Use (async (CTX, next) => {await next(); /* Use (async (CTX, next) => {await next(); }); To add middleware */
 use(fn) {
 if (isGeneratorFunction(fn)) {
 fn = convert(fn); // Compatible with KOA1 generator writing, the conversion principle will be explained below
 }
 this.middleware.push(fn); // Store the passed functions in the Middleware array
 return this;
 }
 // Returns a function like (req, res) => {}, which is passed as an argument to the HTTP. createServer function in LISTEN, which is used to process the request
 callback() {
 // Compose all the functions passed into use by koa-compose
 const fn = compose(this.middleware);
 const handleRequest = (req, res) = > {
 // A more powerful CTX package based on REQ and RES is described in more detail below
 const ctx = this.createContext(req, res);
 // Call handleRequest on the app instance
 return this.handleRequest(ctx, fn);
 };
 return handleRequest;
 }
 // Process the request
 handleRequest(ctx, fnMiddleware) {
 // omit, see below
 }
 // A more powerful CTX package based on REQ and RES
 createContext(req, res) {
 // omit, see below}};Copy the code

From the above code, we can conclude that the application.js core actually handles four things:

1. Start the framework

2. Implement the middleware mechanism of onion model

3. Encapsulate a cohesive context

4. Implement a unified error handling mechanism for asynchronous functions

2.2 the context. Js


const util = require('util');
const createError = require('http-errors');
const httpAssert = require('http-assert');
const delegate = require('delegates');
const proto = module.exports = {
 // Omit some unimportant functions
 onerror(err) {
 // Raises an application instance error event
 this.app.emit('error', err, this); }};/ * in application in createContext function, created the context object will mount based on request. The js implementation request object and based on the response. The js realize the response object. The following two delegates allow context objects to delegate parts of the properties and methods of request and response */
delegate(proto, 'response')
 .method('attachment')... .access('status')... .getter('writable')
 ...;
delegate(proto, 'request')
 .method('acceptsLanguages')... .access('querystring')... .getter('origin')
 ...;
 
Copy the code

From the above code, we can conclude that the context.js core actually handles both of these things

1. Error event handling

2. Proxy some properties and methods of Response object and Request object

request.js

Module.exports = {// In the createContext function of application.js, Request objects encapsulate many convenient properties and get methods based on REqheader() {
 return this.req.headers;
 },
 setheader(val) { this.req.headers = val; }, // omits a large number of similar tool properties and methods};Copy the code

The Request object encapsulates a set of convenience properties and methods based on node’s native REQ that can be called when processing a request.

So when you access ctx.request. XXX, you’re actually accessing setters and getters on the request object.

response.js


module.exports = {
 // In the createContext function of application.js, node's native RES is used as an attribute of the Response object (the object encapsulated in Response.js)
 // The Response object is similar to the Request object in that it encapsulates a set of convenient properties and methods based on res
 get body() {
 return this._body;
 },
 set body(val) {
 / / support string
 if ('string'= =typeof val) {
 }
 / / support buffer
 if (Buffer.isBuffer(val)) {
 }
 / / support stream
 if ('function'= =typeof val.pipe) {
 }
 / / support json
 this.remove('Content-Length');
 this.type = 'json'; }},Copy the code

It is worth noting that the body returned supports buffers, streams, Strings, and most commonly JSON, as shown in the example above.

Deep understanding of Koa source code

The following will explain and extend the important details of this process from the perspective of initialization, application startup, request processing, etc. If you thoroughly understand it, you will have a further understanding of KOA, ES6, Generator, Async /await, CO, asynchronous middleware, etc

Initialize the

Koa instantiation:


const Koa = require('koa');
const app = new Koa();

Copy the code

Koa implementation source code:


module.exports = class Application extends Emitter {
 constructor() {
 super(a);this.proxy = false;
 this.middleware = [];
 this.subdomainOffset = 2;
 this.env = process.env.NODE_ENV || 'development';
 this.context = Object.create(context); // Why use object.create? See the reasons below
 this.request = Object.create(request);
 this.response = Object.create(response);
 if (util.inspect.custom) {
 this[util.inspect.custom] = this.inspect; }}}Copy the code

When instantiating KOA, koA does the following two things:

  1. Inheriting Emitter’s ability to handle asynchronous events. How koA will deal with it, however, is still unknown, so there is a question mark.

  2. During instance creation, three objects are initialized as instance properties: Context, Request, and Response. There is also the familiar array mDDleware that houses middleware. Note that this.xxx is assigned with object.create (XXX).

Start the application and process the request

After instantiating KOA, next, pass in the middleware function with app.use:


app.use(async (ctx,next) => {
 await next();
});

Copy the code

Koa execution source code:


use(fn) {
 if (isGeneratorFunction(fn)) {
 fn = convert(fn);
 }
 this.middleware.push(fn);
 return this;
 }
 
Copy the code

When we implement app.use, KoA does these two things:

  1. Check if it is a generator function, if so, use koa-convert for conversion (KOA3 no longer supports generator).
  2. Any methods passed in use are pushed into middleware.

How do I convert a generator function into a class async function

Koa2 is compatible with the KOA1 version, and middleware functions that are generator functions are converted to “class Async functions” using koA-convert. (By the third version, however, this compatibility will be removed).

So how does it work?

What is the difference between generator and Async?

The only difference is that async executes automatically, whereas the Generator calls the next function each time.

So the question becomes, how do I get the generator to automatically execute the next function?

Recall generator: Each time generator’s next function is executed, it returns an object:

{ value: xxx, done: false }
Copy the code

If you can execute next again after returning this object, you can perform automatic execution.

Look at the following example:


function * gen(){
 yield new Promise((resolve,reject){
 // async function 1
 ifResolve () {resolve ()}else{ reject(); }});yield new Promise((resolve,reject){
 // async function 2
 ifResolve () {resolve ()}else{ reject(); }})}let g = gen();
let ret = g.next();

Copy the code

Ret = {value: Promise instance; Done: false}; Value now has the Promise object, so it can define its own success/failure callback. Such as:


ret.value.then((a)= >{
 g.next();
 })

Copy the code

And now we’re done. We just have to find a way to make g.ext () last forever.

So the point is that the yield value must be a Promise. So let’s see how CO converts all of these things into promises:


function co(gen) {
 var ctx = this; // Convert the context to the object currently calling co
 var args = slice.call(arguments.1) // Get parameters
 // we wrap everything in a promise to avoid promise chaining,
 // Whatever your gen is, wrap it with Promise first
 return new Promise(function(resolve, reject) {
 // If gen is a function, change this of gen to this object in CO and execute gen
 if (typeof gen === 'function') gen = gen.apply(ctx, args);
 // Since gen is executed, gen is now an object with next and value. If gen does not exist, or is not a function, gen is returned
 if(! gen ||typeofgen.next ! = ='function') return resolve(gen);
 // Execute code similar to the above example g.ext ()
 onFulfilled();
 function onFulfilled(res) {
 var ret;
 try {
 ret = gen.next(res); // Execute each gen.next()
 } catch (e) {
 return reject(e);
 }
 next(ret); // Pass the return value of the execution into the next function, which is the key to automatic execution
 }
 function onRejected(err) {
 var ret;
 try {
 ret = gen.throw(err);
 } catch (e) {
 return reject(e);
 }
 next(ret);
 }
 /** * Get the next value in the generator, * return a promise. */
 function next(ret) {
 If ret.done=true indicates that the iteration is complete, return the value of the last iteration
 if (ret.done) return resolve(ret.value);
 // Whatever ret.value is, convert it to a Promise and point the context to CTX
 var value = toPromise.call(ctx, ret.value);
 // If value is a Promise, ondepressing will be continued in then. Start from scratch!!
 if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
 return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
 + 'but the following object was passed: "' + String(ret.value) + '"')); }}); }Copy the code

Note the comments to the code above.

From the above code, it can be concluded that the idea of CO is:

Wrapping a generator in a Promise object, and then wrapping its Gen.next () Promise object in that Promise object again, is equivalent to calling Gen.next () repeatedly when the child Promise object completes. Resolve the parent Promise object when all iterations are complete. This becomes a class async function.

This is how to turn a generator function into the content of class async.

Ok, let’s go back to the source code for KOA.

When app.use is executed, the service is not started. Only when app.listen(3000) is executed, the program is really started.

Koa source code:

listen(... args) {const server = http.createServer(this.callback());
 returnserver.listen(... args); }Copy the code

This uses node’s native http.createserver to create the server and passes this.callback() as a parameter.

If written in native Node, the syntax is as follows:


var http = require('http');
http.createServer(function (req, res) {
 res.writeHead(200, {'Content-Type': 'text/plain'});
 res.write('Hello World! ');
 res.end();
}).listen(8080);

Copy the code

In the code above, the http.createserver method passes in a callback function.

This callback function is called each time a new request is received. The parameters req and RES are the requested and returned entities respectively. Req retrieves the received request and RES corresponds to the packet to be returned.

Disadvantages: It is very easy for callback functions to become bloated with the complexity of the business logic, and even if they are broken down into smaller functions, they lose control of the whole process in the complexity of asynchronous callbacks.

Solution: KOA split the business logic into different middleware for processing

Common middleware:

koa router

Koa Logger logs are printed

Koa cors across domains

This.callback () must return the form :(req, res) => {}. Take a look at the this.callback code.


callback() {
 // compose handles all middleware functions. Onion model implementation core
 const fn = compose(this.middleware);
 // Execute function (req, res) on each request => {}
 const handleRequest = (req, res) = > {
 // Encapsulate CTX based on REQ and RES
 const ctx = this.createContext(req, res);
 Call handleRequest to process the request
 return this.handleRequest(ctx, fn);
 };
 return handleRequest;
 }
 handleRequest(ctx, fnMiddleware) {
 const res = ctx.res;
 res.statusCode = 404;
 // Call context.js's onerror function
 const onerror = err= > ctx.onerror(err);
 // Process the response content
 const handleResponse = (a)= > respond(ctx);
 // Ensure that a stream executes the response callback when it closes, completes, and reports an error
 onFinished(res, onerror);
 // Middleware implementation, unified error handling mechanism key
 return fnMiddleware(ctx).then(handleResponse).catch(onerror);
 }

Copy the code

As you can see from the source code above, there are several key details:

  1. Compose (this.Middleware) is doing something (using the KOA-Compose package).

  2. How to implement onion style call?

  3. How is context handled? What does createContext do?

  4. How is koA’s unified error handling mechanism implemented?

Now, to explain one by one.

Koa-compose and onion type call

You can read this in conjunction with the previous article

Single context principle

How is context handled? What does createContext do?

Context is further encapsulated by req and RES in Node’s native HTTP listening callbacks, meaning that koA creates a context for each HTTP request and shares it with all the global middleware. When all the middleware is done, it sends all the data back to RES. Therefore, in each middleware we can retrieve the reQ data for processing, and finally CTX returns the body to RES.

Remember: every request has a unique context object in which everything about the request and response is put.

Here’s how context (CTX) is encapsulated:


// Single context principle
 createContext(req, res) {
 const context = Object.create(this.context); // Create an object that has the prototype method of the context, and so on
 const request = context.request = Object.create(this.request);
 const response = context.response = Object.create(this.response);
 context.app = request.app = response.app = this;
 context.req = request.req = response.req = req;
 context.res = request.res = response.res = res;
 request.ctx = response.ctx = context;
 request.response = response;
 response.request = request;
 context.originalUrl = request.originalUrl = req.url;
 context.state = {};
 return context;
 }

Copy the code

Following the principle of one request for one context, context must exist as a temporary object, everything must be put into one object, so the app, REq, res properties are born from this.

Please pay attention to the above code, why app, REq, RES, CTX are also stored in request, and Response objects?

By making them share the same app, REQ, RES, and CTX at the same time, the purpose is to transfer the processing responsibilities. When users access, only CTX is required to obtain all data and methods provided by KOA, and KOA continues to divide these responsibilities. For example, Request further encapsulates REQ. Response further encapsulates RES, so responsibilities are dispersed and coupling degree is reduced. Meanwhile, sharing all resources makes context highly cohesive, and internal elements are accessible to each other.

In createContext, there is another line like this:


context.state = {};

Copy the code

State is an empty object that holds the state of a single request, allowing you to manage the internal content as needed.

Uniform error handling for asynchronous functions

Next, let’s look at the fourth question: how is koA’s unified error handling mechanism implemented?

Recall how we handled errors uniformly in KOA by simply having the KOA instance listen for onError events. All middleware logic errors are caught and handled here. As follows:

app.on('error', err => {  log.error('server error', err)});
Copy the code

How does this work? The core code is as follows (in the application.js handleRequest function mentioned above) :


handleRequest(ctx, fnMiddleware) {
 const res = ctx.res;
 res.statusCode = 404;
 // application.js also has an onerror function, but this uses context's onerror,
 const onerror = err= > ctx.onerror(err);
 const handleResponse = (a)= > respond(ctx);
 onFinished(res, onerror);
 // This is the key for middleware to execute onError if it fails!!
 return fnMiddleware(ctx).then(handleResponse).catch(onerror);
 }

Copy the code

There are actually two questions:

  1. The onError callback is context.js’s onError function. Why does listening onError on app handle all middleware errors? *

Take a look at context.js’s onerror:

onerror(err) {    this.app.emit('error', err, this); }Copy the code

This. app is a reference to application, and when context.js calls onError, it is the error event that raises the application instance. This event is based on the fact that the Application class inherits from EventEmitter.

  1. How can all middleware errors be handled centrally?

function compose (middleware) {
 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

There is also external processing:

// This is the key for middleware to execute onError if it fails!!
return fnMiddleware(ctx).then(handleResponse).catch(onerror);
Copy the code

It mainly involves the following knowledge points:

  1. The async function returns a Promise object

  2. The async function throws an error that causes the Promise object to become reject. The error thrown is caught by the catch callback (onError above).

  3. If the Promise object following the await command becomes reject, the reject argument can also be caught by the catch callback (onError above).

This explains why KOA implements uniform error handling for asynchronous functions.

Delegate pattern

Finally, the delegation pattern, a design pattern used in KOA.

When we use the context object, we usually use this:

Ctx. header Gets the request header

Ctx. method Method of obtaining a request

Ctx. url Obtains the request URL

The retrieval of request parameters benefits from context. Many of the properties of the request are delegated to the context


delegate(proto, 'request')
 .method('acceptsLanguages')... .access('method')... .getter('URL')
 .getter('header')
 ...;

Copy the code

For instance,

Ctx. body Sets the response body

Ctx. status Sets the response status code

Ctx.redirect () Request redirection

These Settings of response parameters benefit from the fact that many of the methods in KOA context.response are delegated to the context object:


delegate(proto, 'response')
 .method('redirect')... .access('status')
 .access('body')
 ...;

Copy the code

The delegate usage and source code will not be expanded

Why do CTX proxies use a delegate, while response and Request proxies use getters and setters for native RES and REq objects? Because you can write logic in getters and setters, do some processing, and a delegate is just a pure proxy, and it’s essentially implemented based on getters and setters.

Bytedance is hiring a lot of people

Bytes to beat (hangzhou) | Beijing | Shanghai a lot of hiring, welfare super salaries seconds kill BAT, not clock, every day to work in the afternoon tea, free snacks unlimited supply, free meals (I read the menu, Big gate crab, abalone, scallop, seafood, grilled fish fillet, beef, curry and spicy crayfish), free gym, touch bar, 15-inch top, new MBP, monthly rental allowance. This is really a lot of opportunities, the number of research and development after the expansion of N times, good technical atmosphere, cattle, less overtime, still hesitate what? Send your resume to the email below, now!

Just a small part of the JD link below, more welcome to add wechat ~

The front end of jd: job.toutiao.com/s/bJM4Anjob…

The back-end jd: job.toutiao.com/s/bJjjTsjob…

Jd: test job.toutiao.com/s/bJFv9bjob…

Jd: job.toutiao.com/s/bJBgV8job…

The front-end intern: job.toutiao.com/s/bJ6NjAjob…

The back-end intern: job.toutiao.com/s/bJrjrkjob…

Continue to recruit a large number of front-end, server, client, testing, products, internship recruitment are wide to

Resume send [email protected], it is suggested to add wechat Dujuncheng1, you can chat to chat about life, please indicate from nuggets and where to deliver the post