Recently, I need to use the module koa-compose alone. Although I generally know the execution process of middleware when USING KOA, I still feel uneasy about using it without carefully studying the source code (mainly because there are few codes in this module, and I am not interested in studying them if there are many).

Koa-compose looks like a bit of code, but it’s really cumbersome. Closures, recursions, promises… I can’t wrap my head around it. Read a few online interpretation articles, are for a single line of code to explain, or around unclear. And at last resorted to a fool’s way:

Koa-compose removes some comments, type check, source code is as follows:

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, dispatch.bind(null, i + 1)));
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
}
Copy the code

Write the following code:

var index = - 1;
function compose() {
    return dispatch(0)}function dispatch (i) {
      if (i <= index) return Promise.reject(new Error('next() called multiple times'))
      index = i
      var fn = middleware[i]
      if (i === middleware.length) fn = next
      if(! fn)return Promise.resolve('fn is undefined')
      try {
        return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
      } catch (err) {
        return Promise.reject(err)
      }
 }
 
 function f1(context,next){
    console.log('middleware 1');
    next().then(data= >console.log(data));
    console.log('middleware 1');
    return 'middleware 1 return';
  }
  function f2(context,next){
    console.log('middleware 2');
    next().then(data= >console.log(data));
    console.log('middleware 2');
    return 'middleware 2 return';
  }
  function f3(context,next){
    console.log('middleware 3');
    next().then(data= >console.log(data));
    console.log('middleware 3');
    return 'middleware 3 return';
  }
var middleware=[
  f1,f2,f3
]

var context={};
var next=function(context,next){
    console.log('middleware 4');
    next().then(data= >console.log(data));
    console.log('middleware 4');
    return 'middleware 4 return';
};
compose().then(data= >console.log(data));
Copy the code

The direct running results are as follows:

“middleware 1”

“middleware 2”

“middleware 3”

“middleware 4”

“middleware 4”

“middleware 3”

“middleware 2”

“middleware 1”

“fn is undefined”

“middleware 4 return”

“middleware 3 return”

“middleware 2 return”

“middleware 1 return”

Step by step analysis according to the code operation process:

dispatch(0)

I = = 0, the index = = 1 > I index down

index=0

fn=f1

Promise.resolve(f1(context, dispatch.bind(null, 0 + 1)))

This will be implemented

f1(context, dispatch.bind(null, 0 + 1))

Enter the F1 execution context

console.log('middleware 1');

Output middleware 1

next()

It’s the result of calling Dispatch (1) bind

Recursive start

dispatch(1)

I = = 1, the index = = 0 > I index down

index=1

fn=f2

Promise.resolve(f2(context, dispatch.bind(null, 1 + 1)))

This will be implemented

f2(context, dispatch.bind(null, 1 + 1))

Enter the F2 execution context

console.log('middleware 2');

Output middleware 2

next()

I’m just calling dispatch(2)

Then a recursive

dispatch(2)

I = = 2, the index = = 1 > I index down

index=2

fn=f3

Promise.resolve(f3(context, dispatch.bind(null, 2 + 1)))

This will be implemented

f3(context, dispatch.bind(null, 2 + 1))

Enter the F3 execution context

console.log('middleware 3');

Output middleware 3

next()

I’m just calling Dispatch (3)

Then a recursive

dispatch(3)

I = = 3, the index = = 2 > I index down

index=3

i === middleware.length

fn=next

Promise.resolve(next(context, dispatch.bind(null, 3 + 1)))

This will be implemented

next(context, dispatch.bind(null, 3 + 1))

Enter the next execution context

console.log('middleware 4');

Output middleware 4

next()

I’m just calling Dispatch (4)

Then a recursive

dispatch(4)

I = = 4, index = = 3 I > index down

index=4

fn=middleware[4]

fn=undefined

reuturn Promise.resolve('fn is undefined')

Return to the next execution context

console.log('middleware 4');

Output middleware 4

return 'middleware 4 return'

Promise.resolve('middleware 4 return')

Return to the F3 execution context

console.log('middleware 3');

Output middleware 3

return 'middleware 3 return'

Promise.resolve('middleware 3 return')

Return to the F2 execution context

console.log('middleware 2');

Output middleware 2

return 'middleware 2 return'

Promise.resolve('middleware 2 return')

Return to the F1 execution context

console.log('middleware 1');

Output middleware 1

return 'middleware 1 return'

Promise.resolve('middleware 1 return')

Go back to the global context

So far it has been printed

“middleware 1”

“middleware 2”

“middleware 3”

“middleware 4”

“middleware 4”

“middleware 3”

“middleware 2”

“middleware 1”

then

“fn is undefined”

“middleware 4 return”

“middleware 3 return”

“middleware 2 return”

“middleware 1 return”

How did it come about

Looking back, it’s in every middleware

next().then(data=>console.log(data));

Resolve (‘fn is undefined’), then f4, F3, F2,f1. So why is it always last?

Promise.resolve('fn is undefined').then(data= >console.log(data));
console.log('middleware 4');
Copy the code

Let’s just run it and see

or

setTimeout((a)= >console.log('fn is undefined'),0);
console.log('middleware 4');
Copy the code

The entire call can also be viewed like this:

function composeDetail(){
  return Promise.resolve(
    f1(context,function(){
      return Promise.resolve(
        f2(context,function(){
          return Promise.resolve(
            f3(context,function(){
              return Promise.resolve(
                next(context,function(){
                  return Promise.resolve('fn is undefined')
                })
              )
            })
          )
        })
      )
    })
  )
}
composeDetail().then(data= >console.log(data));
Copy the code

Compose’s approach may be silly, but its role is obvious

Reject (new Error(‘next() called multiple times’)) if (I <= index) return promise.reject (new Error(‘next() called multiple times’))

Call next() twice in one piece of middleware.