This paper simply introduces the principle of onion model

The classic example

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

app.use(async (ctx, next) => {
  console.log('1');
  await next();
  console.log(1-1 ' ');
});

app.use(async (ctx, next) => {
  console.log('2');
  await next();
  console.log('2-2');
});

app.use(async (ctx, next) => {
  console.log('3');
  await next();
  console.log('3-2');
});

app.listen(3000);
Copy the code

The output

Access 127.0.0.1:3000 and the result is as follows

1
2
3
32 -
22 -
1- 1
Copy the code

This is the classic KOA onion model


So why?

Refer to the source code to construct oneAppThe class of

class App {
    constructor() {
        // Define the middleware array
        this.middleware = [];
    }

    use(fn) {
        if (fn && typeoffn ! = ="function") throw new Error(The input must be a function.);
        // The fn input is passed to the Middleware array
        this.middleware.push(fn); } listen(... arg) {Const server = http.createserver (this.callback()); const server = http.createserver (this.callback()); * return server.listen(... args); * /
        this.callback();
    }

    callback() {
        const fn = compose(this.middleware);
        return this.handleRequest(fn);
    }

    handleRequest(fnMiddleware) {
        return fnMiddleware()
            .then((a)= > { console.log('over'); })
            .catch((err) = > { console.log(err); }); }}Copy the code
  • AppDefines thethis.middlewareSo an array like this is used to putapp.use(fn)The incoming middleware methodfn
  • app.listen(3000)It’s actually creating oneHTTPServer (this step is omitted for simplicity in this article).this.callback()ishttp.createServer()To handlehttpRequest (ps. Can also passapp.callback()Method to rewrite)
  • this.callback()Well, it’s actually executingcompose(this.middleware)Function returns the result

So let’s seecomposeThis function

// Simplify the compose method in the koa-compose source code
function compose(middleware) {
    return function (context, next) {
        return dispatch(0)
        function dispatch(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

Compose (this.middleware) returns a function fn that traverses the implementing middleware method this.middleware

Using the example at the beginning of this article, let’s see how FN performs

// dispatch(0)
Promise.resolve((async (ctx, next) = > {
    console.log('1');
    await next();
    console.log(1-1 ' ');
})(context, dispatch.bind(null.1)));

// Next in dispatch(0) is dispatch.bind(null, 1)
// So next() is equivalent to dispatch(1)
Promise.resolve((async (ctx, next) = > {
    console.log('2');
    await next();
    console.log('2-1');
})(context, dispatch.bind(null.2)));

// Next in dispatch(1) is dispatch.bind(null, 2)
// So next() is equivalent to dispatch(2)
Promise.resolve((async (ctx, next) = > {
    console.log('3');
    await next();
    console.log('3-1');
})(context, dispatch.bind(null.3)));

// This.middleware[3] does not exist, so a promise.resolve () is returned;
Copy the code

So according to the order of execution of the code, it’s 1, 2, 3, 3-1, 2-1, 1-1

Error trapping

As you can see, fnMiddleware() is followed by then and catch functions, indicating that any errors that occur during middleware execution will be caught by the last catch