usage

Middleware usage within the KOA framework

The synchronous sample

app.use((ctx, next) => {
    console.log(1)
    next()
    console.log(3)
})
app.use((ctx) => {
    console.log(2)
})
//  1 => 2 => 3
Copy the code

Asynchronous sample

const Koa = require('koa'); const app = new Koa(); app.use(async (ctx, next) => { console.log(1); await next(); The console. The log (1.1); }); app.use(async (ctx, next) => { console.log(2); await next(); The console. The log (2.2); }); app.use(async (ctx, next) => { console.log(3); await next(); The console. The log (3.3); });Copy the code

Print the result

// 1/2/3/3.3/2.2/1.1Copy the code

When an application runs to await next(), it suspends the current application and goes to the next piece of middleware, and then comes back to continue processing. That is, when a request comes in, #1 is passed by the first and last, #2 is passed by the second and penultimate, and so on.

Note: Inside each use is a piece of middleware. This middleware can be written by yourself or by many third parties. App.use is often found

The onion model

Examples of practical usage

const Koa = require("koa");
const app = new Koa();
 
// x-response-time
 
app.use(async (ctx, next) => {
  const start = Date.now();
  await next();
  const ms = Date.now() - start;
  ctx.set("X-Response-Time", `${ms}ms`);
});
 
// logger
 
app.use(async (ctx, next) => {
  const start = Date.now();
  await next();
  const ms = Date.now() - start;
  console.log(`${ctx.method} ${ctx.url} - ${ms}`);
});
 
// response
 
app.use(async ctx => {
  ctx.body = "Hello World";
});
 
app.listen(3000);
Copy the code

Basically, all of Koa’s functionality is implemented through middleware.

Simple source code implementation

Use function push middleware function into array;; When next is encountered, the next middleware is executed

Below is the actual source code, which can be glanced down

class Koa { constructor () { this.middlewares = [] } use (callback) { this.middlewares.push(callback) } } var app = new Koa() app.use(async (ctx, next) => { console.log(1) await next() console.log(2) }) app.use(async (ctx, next) => { console.log(3) await next() console.log(4) }) app.use(async (ctx, next) => { console.log(5) }) function compose (middlewares) { let index = -1 return dispatch(0) function dispatch (i) { index = i if (i === middlewares.length) return let fn = middlewares[i] let result = fn(null, Function next() {// null should be context, return dispatch(I +1). Resolve (result)}} compose(app.middlewares)Copy the code

If the above already understand, there is no need to look down…

To source

Let’s start with a simple synchronization example

class Koa { constructor () { this.middlewares = [] } use (fn) { this.middlewares.push(fn) } } var app = new Koa() // Use (function (next) {console.log(1) next() console.log(2)}) }) function compose (middlewares) {function dispatch (index) {if (index === middlewares.length) return Middlewares const fn = middlewares[index] middlewares const fn = middlewares[index] middlewares const fn = middlewares[index] Console.log (1) function next(){// Create a next function and await the next() call. Compose (app.middlewares) compose(index+1)} dispatch(0)} compose(app.middlewares) Function next() {m2()} function m2() {console.log(3)} function m1 () {console.log(1) next() Console.log (2)} m1() // ExecuteCopy the code

Async (async function, await next())

First, let’s take a look at a knowledge point:

async function test() { var res = await ajax() console.log('res', Res)} function ajax() {new Promise(resolve => {resolve(1) console.log(2)}, 2000)})} test() // output // res: undefined // output 2 after 2 secondsCopy the code

You will notice that the RES has no await waiting for resolve after 2 seconds.

This is because ajax doesn’t return in line 7, which means ajax() returns undefined instead of a promise, as in the following

async function test() {
    var res = await ajax()
    console.log('res', res)
}
 
function ajax() {
    setTimeout(() => {
        console.log(2)
    }, 2000)
}
 
test()
Copy the code

In plain English, if ajax() behind await does not return a promise object, the await keyword is considered useless

var res = await ajax()

= = =

var res = ajax()

Therefore, if you want to await an object, you must return a promise object

Start asynchronous code analysis

In the past node did not support async and await, compose’s asynchronous promise is supported, and the code principle is very cumbersome, various.then

However, node currently supports async and await, see below

Change to asynchronous:

Async function next () {return m2()} async function m2() {// async function m2()  { resolve() console.log(3) }, 2000)})} async function m1() {console.log(1) await next() // wait console.log(2)} m1() // execute // 1 // 3 // 2Copy the code

You can see that the output of 2 is indeed awaiting await for 2 seconds. What does that mean?

Await next(), as long as you return a normal promise object, the effect will be the same even if you are async:

What effect? Log (2) will wait for next() to check the synchronization again

Function next() {m2()} function m2() {console.log(3)} function m1 () {console.log(1) next() Next () and console.log(2)} m1() //Copy the code

So for M1, when you go to the next line of code

Synchronization: next() itself will wait for next() to finish before executing the following code

Async: await next(), await await, resolve n seconds later, then execute subsequent code

Conclusion:

Having said all that, what is our purpose? What is the core essence of Compose?

The core essence of the Onion model is:

Function m1 () {console.log(1) next() // to execute the next middleware m2, do not continue the following code!! console.log(2) }Copy the code

Essence: Console.log (2) must be executed after next() is complete

That is, next() executes the next piece of middleware, but must interrupt M1, and then continue M1 when next() completes execution

App.use (async next=>{console.log(1) await next() console.log(1.1)}) app.use(async next=>{console.log(2) await next() Console.log (2.1)}) app.use(async next=>{console.log(3) await next() console.log(3.1)}) app.use(async next=>{console.log(3) await next() console.log(3.1)}) app.use(async next=>{ console.log(4) })Copy the code

Output sequence: 1 2 3 4 3.1 2.1 1.1

Console.log (1) must wait until await next() is complete before executing console.log(1.1)

2, 3, 4, 3.1, 2.1 is next completely executed?

Function m1 () {console.log(1) await next() // Console.log (2)}Copy the code

So the core of asynchrony is to ensure that the await next() is fully executed

Guarantee 1: the await keyword must be written, otherwise invalid

Guarantee 2: Next () must return a Promise object, otherwise it will not work

Therefore, for asynchronous source code, simply modify the synchronous source code to return the Promise object in guarantee 2

Because the await in guarantee 1 is written when we use it, not in source…

Don’t forget:

‘Await’ depends on when ‘resolve’ is included in a later promise

For things I forgot about async and await, I have to read the async principle article

Asynchronous source code:

class Koa { constructor () { this.middlewares = [] } use (fn) { this.middlewares.push(fn) } } var app = new Koa() // Use (async function (next) {console.log(1) await next() console.log(2)}) function ajax () {return new Promise(resolve => { setTimeout(() => { resolve() console.log(5) }, Use (async function (next) {await Ajax () console.log(3)}) function compose (middlewares) {// middleware 2 app.use(async function (next) {await Ajax () console.log(3)}) function compose (middlewares) { Function dispatch (index) {if (index === middlewares. Length) return const fn = middlewares[index] // Let res = fn(function next = fn, function next = fn) Console.log (1) function next(){// Create a next function and await the next() call. Return dispatch(index+1) // To ensure that next() returns a value, If you want to return a promise object, would you have to dispatch()? }) // Note that this is where dispatch() returns the value, corresponding to line 39. We must return a promise for await use. // await next(), the essence of next() is to complete the execution of the next middleware function // so await the promise object, is the middleware function after the execution of the return promise! /* async function (next) {console.log(3)} */ Fn () returns a promise, and fn itself is a function with async keyword that returns a promise! // So if your middleware function writes async directly, return res! Return res // async itself returns a promise, so if fn () returns a mess, // return promise. Resolve (res) : compose(app.middlewares)Copy the code

From line 34 to line 61, notice that middleware 2 also has an await ajax() in order to simulate that next() is an n-second async and await it