koa源码解析

206 阅读1分钟

本文主要从以下几个方面来阐述koa源码

  • koa的context应用
  • koa 中间件机制
  • 错误处理

context应用

koa为了简化API,引入了context概念,将原始请求对象req和响应对象res 封装并且挂载到context上 在context上设置getter 和 setter 从而简化操作。

createContext(req, res) {
    const ctx = Object.create(context)
    ctx.request = Object.create(request)
    ctx.response = Object.create(response)

    ctx.req = ctx.request.req = req
    ctx.res = ctx.response.res = res
    return ctx
}

Object.create 是因为context,request,response对象了里面有getter setter操作

request.js
module.exports = {
    get url() {
        return this.req.url;
    },

    get method() {
        return this.req.method.toLowerCase()
    }
};

response.js
module.exports = {
    get body() {
        return this._body
    },
    set body(val) {
        this._body = val
    }
}

context.js
module.exports = {
    get url() {
        return this.request.url
    },
    get body() {
        return this.response.body
    },
    set body(val) {
        this.response.body = val
    },
    get method() {
        return this.request.method
    }
}

当我调用ctx.method的时候

ctx.method  ->  
context.js / return this.request.method
ctx.request.method  -> 
request.js / this.req.method.toLowerCase()
ctx.request.req.method.toLowerCase() ->
ctx.req = ctx.request.req = req -> 
req.method.toLowerCase()

koa 中间件机制

中间件就是函数式编程的compose应用 将一组异步函数 复合成一个函数 内层函数的返回值就是外层函数的参数 下图是经典的洋葱圈模型

koa-compose代码如下

compose(middlewares) {
    return function (ctx) {
        return dispatch(0)
        function dispatch(i) {
            let fn = middlewares[i]
            if (!fn) {
                return Promise.resolve()
            }
            return Promise.resolve(
                fn(ctx, function next() {
                    return dispatch(i + 1)
                })
            )
        }
    }
}

采用递归来处理 最后大概变成这个样子

const [fn1, fn2, fn3] = this.middleware;
const fnMiddleware = function(context){
    return Promise.resolve(
      fn1(context, function next(){
        return Promise.resolve(
          fn2(context, function next(){
              return Promise.resolve(
                  fn3(context, function next(){
                    return Promise.resolve();
                  })
              )
          })
        )
    })
  );
};
fnMiddleware(ctx).then(handleResponse).catch(onerror);

调用的时候

app.use(async (ctx, next) => {
  ctx.body = "1";
  await next();
  ctx.body += "5";
});

app.use(async (ctx, next) => {
  ctx.body += "2";
  await next();
  ctx.body += "4";
});


上面代码await next() 的时候 await Promsie.resolve() 就是执行下一个中间件 等所有下一个中间件执行完了 再执行 next后面的代码。

koa错误处理

Promise报错通过catch来捕获,最后调用context里面的error emit一个error事件,然后application 里面接受一下 最后打印出来

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

    if (!this.listenerCount('error')) this.on('error', this.onerror);
    ....
}

application.js
const onerror = err => ctx.onerror(err);
fnMiddleware(ctx).then(handleResponse).catch(onerror);
--->

context.js
ctx.onerror
onerror(err) {
	this.app.emit('error', err, this);
}

application.js
this.on('error', this.onerror);

onerror(err) {
  const msg = err.stack || err.toString();
  console.error();
  console.error(msg.replace(/^/gm, '  '));
  console.error();
}