This paper introduces:
- How does KOA handle requests
- Koa – compose function
1. Let’s start with two pictures
This is a common koA onion diagram
The KOA Web server receives an API request; Need to go through one by one handler (request – > | fn1 | – > | fn2 | – > | fn… | – > response). How to chain the processing functions in sequence. In koA, it is the koa-compose function that is relied on.
As shown in the figure below, a request comes in through the permission check, the Controller layer, the Service layer, and so on, and passes through each layer of functions (handled by the middleware) before the result is returned to the interface caller.
2. Relevant source code
Start with a KOA service, through the following demo, with the source code to see how KOA is handling requests.
const Koa = require('koa')
const app = new Koa()
app.use(async (ctx, next) => {
console.log('-- 1 -- -- - >')
await next()
console.log('6 = = = = = = >')
})
app.use(async (ctx, next) => {
console.log('-- -- -- -- - > 2')
await next()
console.log('= = = = = = > 5')
})
app.use(async (ctx, next) => {
console.log('- 3 - >')
await next()
console.log('= = = = = = 4 >')
})
app.listen(8899.() = > {
console.log('Application started')})The result of the execution structure is 1, 2, 3, 4, 5, 6
Copy the code
Take a look at the source code to see how koA handles requests. The following source code is in the koa/lib/application.js directory
1. app.listen()
listen(. args) {
const server = http.createServer(this.callback());
// Call Node's native http.createserver ([requestListener])
// requestListener is a request handler used in response to a request event;
// This function takes two arguments, req,res. This. Callback is executed when a request comes in
returnserver.listen(... args);// Port host information
}
Copy the code
2. Request handler this.callback()
callback() {
// Convert the middleware function to the compose function
const fn = compose(this.middleware);
if (!this.listenerCount('error')) this.on('error'.this.onerror);
// The function that responds to the request event is called the callback function
const handleRequest = (req, res) = > {
// Create context according to req, res
const ctx = this.createContext(req, res);
// Eventually the handleRequest method is called, passing in the context and the compose function
return this.handleRequest(ctx, fn);
};
return handleRequest;
}
Copy the code
3. Array of middleware functions this.Middleware
use(fn) {
if (typeoffn ! = ='function') throw new TypeError('middleware must be a function! ');
// Add functions to the Middleware array when use is called
this.middleware.push(fn);
return this;
}
Copy the code
4. Callback function entity this.handlerequest
handleRequest(ctx, fnMiddleware) {
const res = ctx.res;
res.statusCode = 404;
const onerror = err= > ctx.onerror(err); // Handle the error
const handleResponse = () = > respond(ctx); // Process the returned result
onFinished(res, onerror);
Compose: compose compose compose compose compose compose compose compose compose compose
return fnMiddleware(ctx).then(handleResponse).catch(onerror);
}
Copy the code
The core of the process is to call the compose function and convert the array of middleware functions to the compose function const fn = compose(this.middleware); Fn is then called, which executes all middleware functions in turn; The context CTX traverses all middleware functions.
3. com pose function
First take a look at the koa-compose function source
function compose (middleware) {
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! ')}/ * * *@param {Object} context
* @return {Promise}
* @api public* /
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, dispatch.bind(null, i + 1)));
} catch (err) {
return Promise.reject(err)
}
}
}
}
Copy the code
If you remove some of the comments, such as fault tolerance, you can see that the koa-compose function has more than a dozen lines of code
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()
return Promise.resolve(fn(context, function next () {
return dispatch(i + 1)}))}}Copy the code
See the koa-compose function in action with the following four simple functions
async function a (context,next) {
console.log('1. Before function [a] next - execute..... ')
await next()
console.log('8. After function [a] next - execute..... ')}async function b (context, next) {
console.log('2. Before function [b] next - execute..... ')
await next()
console.log('7. After function [b] next - execute..... ')}async function c (context, next) {
console.log('3. Before function [c] next - execute..... ')
await next()
console.log('6. After function [c] next - execute..... ')}async function d (context, next) {
console.log('4. Function [d] next before - execute..... ')
console.log('5. After function [d] next - execute..... ')}Copy the code
Viewing the Result
var composeMiddles1 = compose([a,b,c,d])
composeMiddles()
// Execution result
// 1. Before function [a] next -.....
// 2. Before function [b] next - execute.....
// 3. Before function [c] next - execute.....
// 4. Function [d] next before - execute.....
// 5. After function [d] next - execute.....
// 6. After function [c] next - execute.....
After function [b] next - execute.....
After function [a] next - execute.....
Copy the code
To visualize this, we use the following pseudocode
async function a (context,next) {
console.log('1. Before function [a] next - execute..... ')
async function b (context, next) {
console.log('2. Before function [b] next - execute..... ')
async function c (context, next) {
console.log('3. Before function [c] next - execute..... ')
async function d (context, next) {
console.log('4. Function [d] next before - execute..... ')
console.log('8. After function [d] next - execute..... ')}console.log('7. After function [c] next - execute..... ')}console.log('6. After function [b] next - execute..... ')}console.log('5. After function [a] next - execute..... ')}Copy the code
4.compose function analysis
// An array of middleware functions
function compose (middleware) {
return function (context, next) { // Calling the compose function returns a function
let index = -1
return dispatch(0) // Starts the first function in the Middleware array
function dispatch (i) {
index = i
let fn = middleware[i]
if (i === middleware.length) {
fn = next
}
if(! fn)return Promise.resolve()
// this is to execute a, b,c,d functions and await next()
return Promise.resolve(fn(context, function next () {
return dispatch(i + 1) // Execute the next middleware function}}}}))ComposeMiddles1 is a function that contains all the middleware functions passed in and can execute them in sequence
var composeMiddles1 = compose([a,b,c,d])
composeMiddles() // Execute dispatch(0)
dispatch(0) // Start the execution of the first function (a)
// await next() in a function
async function a (context,next) {
console.log('1. Before function [a] next - execute..... ')
await next() Function next () {return dispatch(I + 1)}
console.log('8. After function [a] next - execute..... ')}await next()
// It is executed
dispatch(i + 1)
// dispatch(1) is the b function
// In function b
await next()
/ /...
// This will be executed in sequence
Copy the code