Koa 解析( 二 )

错误处理逻辑在 Koa 内部 , 由于是洋葱圈模型 , 正常的中间件处理过程可以不处理报错逻辑 , 只需要在中间件数组的第一个中间件设置为错误函数中间件即可 。 而在 Express 4 中 , 需要在每一个正常的中间件中都包含错误处理逻辑 , 并且需要通过 next() 函数抛出来 , 否则该错误将会被隐没 。
更小的核心代码与 Express 4 包含有齐整的 Web 工具(路由、模板等)不同 , Koa 只专注于核心代码 。 因此它的体积更小 。
Koa 解析简单的描述并不能让我们对 Koa 的原理有更深的理解 。 我们尝试来看看源码 。 最简单的演示代码:
const Koa = require('koa');const app = new Koa();// responseapp.use(ctx => {ctx.body = 'Hello Koa';});console.log("start the test1 server !");app.listen(3000);在这里 , 我们想要去源码上弄明白几个事情:
app.use()app.listen()Koa 初始化应用实例Koa 应用中 , 我们通过 const app = new Koa() 来构建一个实例应用 。 核心代码(有删减):
constructor(options) {super();this.middleware = [];// 每一个 app 实例 , 都有下面三个对象的实例this.context = Object.create(context);this.request = Object.create(request);this.response = Object.create(response);if (util.inspect.custom) {this[util.inspect.custom] = this.inspect;}}初始化应用实例的过程可以看出:为 app 实例添加 context 、 request 、 response 、 middleware 等属性 。在这个初始化的过程中 , Koa 会把 Koa 官方提供的方法都挂载到相应的位置 , 方便在代码中调用 。
app.use() 添加中间件use(fn) {if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');if (isGeneratorFunction(fn)) {deprecate('Support for generators will be removed in v3. ' +'See the documentation for examples of how to convert old middleware ' +'');fn = convert(fn);}debug('use %s', fn._name || fn.name || '-');// 直接存入到 middleware 数组中 , 后续统一处理this.middleware.push(fn);return this;}在这里会检查传入函数的类型 , 如果是老的 Generator 函数类型会转换一下 , 然后直接放到 middleware 这个数组中 。 数组中的中间件 , 会在每一个请求中去挨个执行一遍 。
app.listen() 监听--核心逻辑在 listen 函数执行的时候 , 才会创建 server :
listen(...args) {debug('listen');const server = http.createServer(this.callback());return server.listen(...args);}在 Node 的 http 模块 , 对于每一个请求 , 都会走到回调函数 callback 中去 。 所以这个 callback 是用于处理实际请求的 。 我们来看看 callback 做了啥:
callback() {// 包装所有的中间件 , 返回一个可执行的函数 。 koa-compose 实现了洋葱圈模型const fn = compose(this.middleware);if (!this.listenerCount('error')) this.on('error', this.onerror);const handleRequest = (req, res) => {// req res 是 node 原生请求参数const ctx = this.createContext(req, res);// 将创建的 ctx 返回 , 传给所有中间件 , 作为整个请求的上下文return this.handleRequest(ctx, fn);};return handleRequest;}这里的内容并不简单 , 涉及到几个点:
createContextcomposethis.handleRequest(ctx, fn)解析 createContextcreateContext(req, res) {// 每一个请求对应一个 ctx、request、response、req、resconst context = Object.create(this.context);const request = context.request = Object.create(this.request);const response = context.response = Object.create(this.response);context.app = request.app = response.app = this;// 挂载 node 原生请求参数 req res 到 context、request、response 上context.req = request.req = response.req = req;context.res = request.res = response.res = res;request.ctx = response.ctx = context;request.response = response;response.request = request;context.originalUrl = request.originalUrl = req.url;context.state = {};return context;}