Comparison between express and koa middleware modes, and koa Middleware
Cause
Recently, I have been learning how to use koa. because koa is a fairly basic web framework, most of the things required by a complete web application are introduced in the form of middleware, such as koa-router, koa-view. As mentioned in the koa document, the middleware mode of koa is different from that of express. koa is of the onion type and express is of the straight line type. As for why, many articles on the Internet do not have specific analysis. Or simply async/await features. Let's not talk about the right or wrong of such a statement. It is too vague for me. So I decided to analyze the principles and usage of the middleware implementation of the two through the source code.
For the sake of simplicity, express here is replaced by connect (The implementation principle is consistent)
Usage
Both are subject to the official website (github) documentation.
Connect
The usage of the official website is as follows:
var connect = require('connect');var http = require('http');var app = connect();// gzip/deflate outgoing responsesvar compression = require('compression');app.use(compression());// store session state in browser cookievar cookieSession = require('cookie-session');app.use(cookieSession({ keys: ['secret1', 'secret2']}));// parse urlencoded request bodies into req.bodyvar bodyParser = require('body-parser');app.use(bodyParser.urlencoded({extended: false}));// respond to all requestsapp.use(function(req, res){ res.end('Hello from Connect!\n');});//create node.js http server and listen on porthttp.createServer(app).listen(3000);
According to the document, we can see that connect provides simple routing functions:
app.use('/foo', function fooMiddleware(req, res, next) { // req.url starts with "/foo" next();});app.use('/bar', function barMiddleware(req, res, next) { // req.url starts with "/bar" next();});
Connect middleware is linear. after next, we continue to look for the next middleware. This mode is intuitively understandable. middleware is a series of arrays, and the corresponding routing processing method through routing matching is middleware. In fact, connect is also implemented in this way.
App. use is to add a new middleware to the middleware array. The execution of middleware relies on the private method app. handle for processing. The same is true for express.
Koa
Compared with connect, koa's middleware model is not so intuitive. The figure on the Internet shows:
That is, after koa finishes processing the middleware, it will return for a while, which gives us a larger operation space. Let's take a look at the koa official website instance:
const Koa = require('koa');const app = new Koa();// x-response-timeapp.use(async (ctx, next) => { const start = Date.now(); await next(); const ms = Date.now() - start; ctx.set('X-Response-Time', `${ms}ms`);});// loggerapp.use(async (ctx, next) => { const start = Date.now(); await next(); const ms = Date.now() - start; console.log(`${ctx.method} ${ctx.url} - ${ms}`);});// responseapp.use(async ctx => { ctx.body = 'Hello World';});app.listen(3000);
Obviously, when the koa processing middleware encounters await next (), it will suspend the current middleware and process the next middleware. Finally, it will continue to process the remaining tasks, however, intuitively, we will have a feeling of implicit familiarity: Isn't it a callback function. The specific implementation method is not mentioned here, but it is indeed the callback function. It has nothing to do with the features of async/await.
Source code analysis
The core difference between the connect and koa middleware modes lies in the implementation of next. Let's take a look at the implementation of next.
Connect
The source code of connect is quite a few comments, and it seems very clear that the connect middleware processing lies in the private method proto. handle, and next is also implemented here.
// Middleware index var index = 0 function next (err) {// incrementing var layer = stack [index ++]; // submit it to other parts for processing if (! Layer) {defer (done, err); return;} // route data var path = parseUrl (req ). pathname | '/'; var route = layer. route; // recursive // skip this layer if the route doesn't match if (path. toLowerCase (). substr (0, route. length )! = Route. toLowerCase () {return next (err);} // call the layer handle call (layer. handle, route, err, req, res, next );}
After removing the obfuscation code, we can see that the next implementation is also very concise. Find the middleware in a recursive call sequence. Call next constantly. The code is quite simple, but the idea is worth learning.
Done is a third-party processing method. Other sub apps and routes are deleted. Not important
Koa
Koa extracts the implementation of next from a separate package, which makes the code simpler, but implements a more complex function.
function compose (middleware) { return function (context, next) { // last called middleware # let index = -1 return dispatch(0) function dispatch (i) { index = i try { return Promise.resolve(fn(context, function next () { return dispatch(i + 1) })) } catch (err) { return Promise.reject(err) } } }}
Looking at the code that has been processed above, some of you may still be confused.
Let's proceed with the process:
function compose (middleware) { return function (context, next) { // last called middleware # let index = -1 return dispatch(0) function dispatch (i) { index = i let fn = middleware[i] if (i === middleware.length) { fn = next } if (!fn) return return fn(context, function next () { return dispatch(i + 1) }) } }}
In this way, the program is simpler and has nothing to do with async/await. Let's take a look at the results.
var ms = [ function foo (ctx, next) { console.log('foo1') next() console.log('foo2') }, function bar (ctx, next) { console.log('bar1') next() console.log('bar2') }, function qux (ctx, next) { console.log('qux1') next() console.log('qux2') }]compose(ms)()
Execute the above program and we can find the output in sequence:
Foo1
Bar1
Qux1
Qux2
Bar2
Foo2
This is also the so-called koa's onion model. Here we can draw a conclusion that the koa's middleware model is not actually related to async or generator, but koa emphasizes async first. The so-called middleware pause is only the reason for the callback function (in my opinion, there is no difference between promise. then and callback, and even async/await is also a form of callback ).
The above is all the content of this article. I hope it will be helpful for your learning and support for helping customers.