Moment For Technology

Step by step to read koA source code, middleware implementation principle

Posted on May 27, 2023, 4:20 a.m. by Kenneth Bruce
Category: The front end Tag: node.js koa

Step by step to read koA source code, middleware implementation principle

Koa middleware performs process control, code is very subtle. This is illustrated by a picture of the onion model. Remember this picture.

Why is this graph like this? Let's have an example to describe it

const Koa = require('koa') const app = new Koa() // fn1 app.use(async (ctx, next) = { console.log('fn1-1') next() console.log('fn1-2') }) // fn2 app.use(async (ctx, next) = { console.log('fn2-1') next() console.log('fn2-2') }) // fn3 app.use(async (ctx, Next) = {console.log('fn3-1') next() console.log('fn3-2')}) app.listen(4002) // FN1-1, Fn2-1, fn3-1, fn3-2, fn2-2, fn1-2

In the example above, the sequence printed is FN1-1, FN2-1, FN3-1, fN3-2, FN2-2, FN1-2, now only know that calling next() will move the control flow to the next middleware until all is done and then the code after the previous next is progressively executed. This one bears a strong resemblance to an onion (if you would like to peel my heart layer by layer).


But how does it work? Let's explore it step by step.

The first is to call the app.use(fn) line, which is in the source code, and delete some code judgment, which looks like this

constructor() { super(); this.middleware = []; } use(fn) { this.middleware.push(fn); return this; }

Push all the functions into an array of middleware, and that's what use does.

Now that we know what use does, we have a lot of middleware functions in our middleware toolbox, so let's move on.

After executing the app.listen function, the code looks like this

listen(...args) { // 创建一个server const server = http.createServer(this.callback()); return server.listen(...args); }

We see that this.callback() executes the function, and we jump to that function.

Callback () {const fn = compose(this.middleware); callback() {const fn = compose(this.middleware); const handleRequest = (req, res) = { const ctx = this.createContext(req, res); Return this.handlerequest (CTX, fn); return this.handlerequest (CTX, fn); }; return handleRequest; }

The callback executes the compose function and passes in an array of middleware parameters.

Now that we've executed the compose function, let's take a look at what's in compose.

The compose function initially refers to the koa-compose module. After simplification, the following code is found, which is a simple 20-something line of code. The following code will be explained in detail later.

function compose (middleware) { return function (context, next) { let index = -1 return dispatch(0) function dispatch (i) { index = i let fn = middleware[i] if (i === middleware.length) fn = next if (! fn) return Promise.resolve() try { return Promise.resolve(fn(context, function next () { return dispatch(i + 1) })) } catch (err) { return Promise.reject(err) } } } }

Executing this compose returns a function, which is the core function. Note that this is called by the callback above. I get a f sub n function

See the above callback call

Then call this.handlerequest (CTX, fn); This function takes CTX and fn (which is the compose function above) as arguments to this.handleRequest. Here's the code.

handleRequest(ctx, fnMiddleware) { return fnMiddleware(ctx).then(handleResponse).catch(onerror); }

This is where compose actually executes the function returned by compose to pass CTX in. Then we move on to fnMiddleware(CTX), which looks something like this.

Function (context, next) {return dispatch(0); // Dispatch (0); // dispatch(0); Function dispatch (I) {// Assign the passed value to index index = I // take the i-th middleware function, Let fn = middleware[I] // Assign next to FN if (I === middleware. Length) fn = next // If FN is false return return Promise.resolve() if (! Fn) return promise.resolve () try {// return promise.resolve, Return promise. resolve(fn(context, Return dispatch(I + 1)})} Catch (err) {return promise.reject (err)}}

The code above is the distillation of this section. We define an index and dispatch function, call dispatch(0) first, assign 0 to index, and take the 0th middleware function from the middleware array (we have three middleware functions in this example), assign it to FN. Execute after both ifs fail

Return promise.resolve (fn(context, function next () {// return dispatch(I + 1)}))

Here the fn middleware function is executed and CTX and function next () {return dispatch(I + 1)} are passed in as arguments. At this point the code looks like the following

App.use (async (CTX, next) = {console.log('fn1-1') next() // Next console.log(' FN1-2 ')})

The next() function will call dispatch(I + 1), which calls fn = middleware[1], the second middleware.

And you get the idea. Then the second middleware executes fn, prints OUT FN 2-1, continues to execute next() function, next function continues to call Dispatch (I + 1),

The third middleware function, fn = middleware[2], prints OUT FN3-1 and continues with next() calling dispatch(I + 1), fn = middleware[3],

If (I === middleware.length) fn = next, Next is fnMiddleware(CTX). Then (handleResponse). Catch (onError); Fn is called, but it is not called, so fn is undefined, and continues to if (! Fn) return promise.resolve () returns an empty value, which is the result of next execution of the third middleware,

Then the next line prints OUT FN3-2, and finally up to FN2-2, and then to FN1-2, the entire middleware execution process. It's like an onion, layer by layer, layer by layer.

Well, the entire middleware execution process is what it is

The last amway blog:

About (Moment For Technology) is a global community with thousands techies from across the global hang out!Passionate technologists, be it gadget freaks, tech enthusiasts, coders, technopreneurs, or CIOs, you would find them all here.