Koa 中的 next

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.use(ctx => {
  ctx.body = 'Hello Koa';
});

app.listen(3000);
// 输出 1 2 3 4 5 6

Koa 中使用 use 函数注册中间件, 并按照注册注册顺序调用
application.js use 函数中可以看到, 传入的中间件被放入了一个 middleware 队列中

use (fn) {
  ...
  this.middleware.push(fn)
  ...
}

application.js callback 函数中使用 koa-compose 来处理 middleware 队列

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

接下来看看 compose 函数

// 省略了一些错误判断
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)
      }
    }
  }
}

从上面的函数可以看出, next 函数是由 dispatch.bind(null, i + 1) 这句语句生成的
使用 next 则调用下一个中间件, 若不使用 next 则执行完当前中间件就会返回 并且在调用完成之后还能再返回到原来的中间件(即洋葱模型)

将上面的函数拆解并精简之后, 方便理解

const middleware = [];

function use(fn) {
    middleware.push(fn);
}

function dispatch(i) {
    const fn = middleware[i];
    if (!fn) return
    return fn(dispatch.bind(null, i + 1));
}

function compose() {
    return dispatch(0);
}

use((next) => {
    console.log(1);
    next();
    console.log(6);
})
use((next) => {
    console.log(2);
    next();
    console.log(5);
})
use((next) => {
    console.log(3);
    next();
    console.log(4);
})
compose()
// 输出 1 2 3 4 5 6

总结

  • 中间件代码必须调用 next() 方法, 后续中间件才会执行
  • 中断剩余中间件即不调用 next() 方法
  • 中间件调用 next() 方法之后, 会先执行后面的中间件最后再执行当前中间件的剩余代码
  • 中间件可以是 async 函数, 因为 dispatch 函数使用了 Promise 返回