I What is an onion model?
Look first 🌰
const Koa = require('koa'); const app = new Koa(); const PORT = 3000; // #1 app.use(async (ctx, next)=>{ console.log(1) await next(); //Pause the current program console.log(1) }); // #2 app.use(async (ctx, next) => { console.log(2) await next(); console.log(2) }) app.use(async (ctx, next) => { console.log(3) }) app.listen(PORT); console.log(`http://localhost:${PORT}`); // output 1 2 3 2 1
In koa, app The callback of the middleware registered by use is iterated according to the onion model. The middleware is divided into two parts by the next() method. The upper part of the next() method will be executed first. The following part of the next() method will be executed after the execution of the following middleware is completed. Combined with the following figure, it can be seen intuitively:
Each layer in the onion represents an independent middleware, which is used to realize different functions, such as exception handling, cache processing, etc. Each request will pass through each layer of middleware from the left. After entering the innermost layer of middleware, it will return layer by layer from the innermost layer of middleware. Therefore, for each layer of middleware, there are two timing points to add different processing logic in a request and response cycle.
Middleware execution sequence:
What can onion models do?
For example, calculating the time-consuming of a request and obtaining the information of other middleware can not be realized without the onion model.
II koa principle
Learn the principle of koa deeply. By consulting the source code of koa, we can see the following methods.
- use method: maintain the middleware array
use (fn) { if (typeof fn !== 'function') throw new TypeError('middleware must be a function!') debug('use %s', fn._name || fn.name || '-') this.middleware.push(fn) return this }
- listen method: node JS native http module createServer method creates a service.
- Callback method: the callback of the service is the callback method, and the return is a Promise function.
listen (...args) { debug('listen') // Create a service const server = http.createServer(this.callback()) return server.listen(...args) } callback () { // Returns a function const fn = compose(this.middleware) if (!this.listenerCount('error')) this.on('error', this.onerror) const handleRequest = (req, res) => { // Create ctx context const ctx = this.createContext(req, res) return this.handleRequest(ctx, fn) } // Execute the Promise function returned in the compose function and return the result. return handleRequest }
Compose comes from the koa compose module. The dispatch in the method traverses the entire middleware, and then passes the context and dispatch(i + 1) to the methods in middleware.
Main purpose [core of onion model]:
1). Pass the context all the way to the middleware
2). Take the next middleware fn in middleware as the return value of next in the future
function compose(middleware) { return function (context, next) { // last called middleware # 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, function next() { return dispatch(i + 1) })) } catch (err) { return Promise.reject(err) } } }
The createContext method and handleRequest method bind ctx with middleware
handleRequest (ctx, fnMiddleware) { const res = ctx.res res.statusCode = 404 const onerror = err => ctx.onerror(err) const handleResponse = () => respond(ctx) onFinished(res, onerror) return fnMiddleware(ctx).then(handleResponse).catch(onerror) } createContext (req, 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 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 }
III reference material
- https://juejin.cn/post/7012031464237694983
- https://juejin.cn/post/7046713578547576863
- https://juejin.cn/post/6890259747866411022