Koa is introduced
On Koa’s official website, the slogan is: The next Generation Web development framework based on the Node.js platform. As we all know, Koa and Express both came from the same team. How do I parse this slogan?
All of the words in this slogan that contain the meaning “compare” can be understood as relative to Express. The so-called “next generation”, “smaller”, “faster”, all have their own meanings. Let’s take a look at Express vs. Koa downloads at this point:
As you can see, Express’s market share and downloads (and growth) are much higher than Koa’s. So it’s safe to call yourself “the next generation” (though it’s debatable whether this statistical comparison is fair).
Also, unlike Express, which includes a basic business processing framework internally, Koa contains only the middleware core processing logic known as the onion Ring model. Koa expects more business modules to be refined by the community. In this way, it is more appropriate to compare Koa to the Connect that was built in before Express (Connect has been removed from Express 4 internally). However, the “smaller” Koa is more clearly defined and has some outstanding features that make developers happy.
Next, let’s take a closer look at how Koa differs from Express. Only by understanding the differences can we better understand the characteristics of Koa.
self-image
On Koa’s official website, there is a description of Koa’s position:
Philosophically, Koa aims to “fix and replace node”, whereas Express “augments node”. Koa uses promises and async functions to rid apps of callback hell and simplify error handling. It exposes its own ctx.request and ctx.response objects instead of node’s req and res objects.
Express, on the other hand, augments node’s req and res objects with additional properties and methods and includes many other “framework” features, such as routing and templating, which Koa does not.
An excerpt from an online translation reads as follows:
Conceptually, Koa is about “fixing and replacing Node,” while Express is about “enhancing Node.” Koa uses promise and async functions to get rid of callback hell and simplify exception-handling logic. It exposes its ctx.request and ctx. Response objects instead of Node’s REq and RES objects.
Express, on the other hand, enhances Node’s REq and RES objects by adding additional properties and methods, and introduces many framework features, such as routing and templates, that Koa does not.
Whether its implementation conforms to the positioning, you can see by yourself.
Middleware processing logic
In essence, Koa differs from Express 4 in the following two points:
- Koa is to use
Promise
+async/await
To handle middleware delivery logic; Express 4 uses callback functions to handle middleware delivery logic. - Koa goes through all the middleware processing logic before finally returning the request; Express 4 is encountered
res.send()
Return the request.
With Promise support for async/await writing in the new ES specification, Koa is perfectly capable of supporting asynchronous processing and getting rid of callback hell. In the following simulation implementation, you can see the difference between the two by looking at the code.
// Express and Koa deal with the underlying simulation of middleware logic
// express: callback function
app.use(function middleware1(req, res, next) {
console.log('middleware1 start')
// next()
(function (req, res, next) {
console.log('middleware2 start')
// next()
(function (req, res, next) {
console.log('middleware3 start')
// next()
(function handler(req, res, next) {
res.send("end")
console.log('123456')
})()
console.log('middleware3 end')
})()
console.log('middleware2 end')
})()
console.log('middleware1 end')})// Koa: Based on Promise + async/await
function compose() {
return function () {
const ctx = {}
// Each call to next() wraps a promise.resolve layer
Promise.resolve(function fn1(context){
console.log("Middleware1 start");
yield Promise.resolve(function fn2(context){
console.log("Middleware2 start");
yield Promise.resolve(function fn3(context){
console.log("Middleware3 start");
yield Promise.resolve(function fn4(context){
/ /... more
});
console.log("Middleware3 end");
});
console.log("Middleware2 end");
})
console.log("Middleware1 end"); }); }}Copy the code
Error handling logic
Within Koa, due to the Onion circle model, normal middleware processing can not handle error logic, just set the first middleware in the middleware array as error function middleware. In Express 4, error handling logic needs to be included in every normal piece of middleware, and it needs to be thrown out via the next() function, otherwise the error will disappear.
Smaller core code
Unlike Express 4, which includes neat Web tools (routing, templates, and so on), Koa focuses only on core code. So it has a smaller volume.
Koa parsing
Simple descriptions do not give us a deeper understanding of how Koa works. Let’s try to look at the source code. The simplest demo code:
const Koa = require('koa');
const app = new Koa();
// response
app.use(ctx= > {
ctx.body = 'Hello Koa';
});
console.log("start the test1 server !");
app.listen(3000);
Copy the code
Here, we want to understand a few things about the source code:
- When you initialize, what do you do?
app.use()
What did you do?app.listen()
What did you do?
Koa Initializes the application instance
In the Koa application, we build an instance application with const app = new Koa(). Core code (deleted) :
constructor(options) {
super(a);this.middleware = [];
// Every app instance has instances of the following three objects
this.context = Object.create(context);
this.request = Object.create(request);
this.response = Object.create(response);
if (util.inspect.custom) {
this[util.inspect.custom] = this.inspect; }}Copy the code
To initialize an app instance, add context, Request, Response, middleware, and other properties to the app instance. During this initialization, Koa will mount any methods that Koa officially provides into their respective locations for easy invocation in code.
App.use () adds middleware
use(fn) {
if (typeoffn ! = ='function') throw new TypeError('middleware must be a function! ');
if (isGeneratorFunction(fn)) {
deprecate('Support for generators will be removed in v3. ' +
'See the documentation for examples of how to convert old middleware ' +
'https://github.com/koajs/koa/blob/master/docs/migration.md');
fn = convert(fn);
}
debug('use %s', fn._name || fn.name || The '-');
// Store it directly into the Middleware array for further processing
this.middleware.push(fn);
return this;
}
Copy the code
This checks the type of the incoming function, converts it if it’s an old Generator function type, and puts it directly into the Middleware array. The middleware in the array executes each request one by one.
App.listen () listening — core logic
The server is created when the listen function executes:
listen(. args) {
debug('listen');
const server = http.createServer(this.callback());
returnserver.listen(... args); }Copy the code
In Node’s HTTP module, for each request, it goes to the callback function. So this callback is used to handle the actual request. Let’s see what callback does:
callback() {
// Wrap all middleware, return an executable function. Koa-compose implements the onion ring model
const fn = compose(this.middleware);
if (!this.listenerCount('error')) this.on('error'.this.onerror);
const handleRequest = (req, res) = > {
// req res is a node native request parameter
const ctx = this.createContext(req, res);
// Return the created CTX to all middleware as the context for the entire request
return this.handleRequest(ctx, fn);
};
return handleRequest;
}
Copy the code
It’s not simple here, it involves a few points:
createContext
What did you docompose
How is the Onion model implementedthis.handleRequest(ctx, fn)
What did
Parsing createContext
createContext(req, res) {
// Each request corresponds to a CTX, request, response, req, or RES
const context = Object.create(this.context);
const request = context.request = Object.create(this.request);
const response = context.response = Object.create(this.response);
context.app = request.app = response.app = this;
// Mount the node native request parameter req res to context, request, and Response
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
The main thing here is to specify what is mounted by the context, which is generated on every request. From the code:
- Per application instance
app
There will be corresponding onescontext
,request
,response
Instance (i.e.this.xxx
). Each request creates its own instance based on these instances. The goal is not to contaminate global variables. - will
node
The nativereq
,res
As well asthis
Mount to thecontext
,request
,response
On. - Will create the
context
Returns, passing in the first argument of any middleware as the context for this request.
Focus on point 2, why these properties are mounted. In addition to the convenience, we can also know by looking at the source resquest.js response.js file: all these accesses are proxies, and ultimately access node’s native REq and RES.
Koa-compose implements the onion ring model
Next, let’s look at how the koa-compose plug-in implements the onion ring model in the source code:
function compose (middleware) {
// Type check
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) {
// The middleware that was last executed
let index = -1
return dispatch(0)
function dispatch (i /* indicates which middleware */ is expected to be executed) {
if (i <= index) return Promise.reject(new Error('next() called multiple times'))
index = i
let fn = middleware[i]
if (i === middleware.length) fn = next
// Without fn, return a reolved Promise object
if(! fn)return Promise.resolve()
try {
/* Const next = dispatch. Bind (null, I + 1); /* Const next = dispatch. Bind (null, I + 1); const fnResult = fn(context, next); return Promise.resolve(fnResult); * /
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
} catch (err) {
return Promise.reject(err)
}
}
}
}
Copy the code
In a nutshell, incompose
According to the variablei
Execute the middleware one by one, in a recursive fashion. The process is not difficult to understand and the implementation is clever.It’s worth noting that each dispatch() returns a Promise. See the GIF below to see how the above code is implemented.
Parsing handleRequest
Now that we have the context information for each request and the middleware logic that flows through compose, let’s see how the request is ultimately handled:
handleRequest(ctx, fnMiddleware) {
const res = ctx.res;
res.statusCode = 404;
const onerror = err= > ctx.onerror(err);
const handleResponse = () = > respond(ctx);
onFinished(res, onerror);
// Execute the middleware function after compose and respond
return fnMiddleware(ctx).then(handleResponse).catch(onerror);
}
Copy the code
Implement the middleware function behind compose and finally implement the respond method. As you can see, the response is wrapped and assigned to context.res.end(body).
conclusion
To sum up, we briefly introduced Koa; At the same time, through the analysis of the Demo file execution process, we see some of the Koa related principles from the source level. So far, we have covered the introduction and analysis of Koa. I write this article in the process, will be more understanding of the whole process, also hope to help you!