onedaywen / tech-blog

技术博客,主要涉及javascript,nodejs,vue,webpack,vuex,vue-router等
0 stars 0 forks source link

koa2.5.1源码解析 #40

Open onedaywen opened 6 years ago

onedaywen commented 6 years ago

第一步

原生nodejs创建http服务器,监听端口3000,触发handleRequest函数。对于koa来说,同样是监听端口,在实际运用中,handleRequest会包含大量的逻辑代码,因此要进行拆分成多个函数。 koa的实现就是对这个“拆分”的实现。handleRequest拆分成了多个中间件函数,通过next作为控制开关,是否流转到下一个中间件。

// 例1
const http = require('http')
const handleRequest = (req, res) => {
  res.end('hello world')
}
const server = http.createServer(handleRequest)
server.listen(3000, () => {
  console.log('server is running at 3000')
})

koa实现一个简单的服务器

const Koa = require('koa')
const app = new Koa()
app.use((ctx, next) => {
  ctx.body = 'hello world'
})
app.listen(3000)
  • 首先app.use(fn)接受一个函数作为参数,但是不立即执行,而是在listen到监听的端口时触发。因此很容易推理, use方法是将函数存储在数组中、或对象中。
  • 对于koa源码的解析,首先应当是从监听端口处开始看,app.listen,而app又是Koa的实例。因此要require('koa'),对外export出来的变量。
  • 打开node_modules中的koa包,在package.json中的main字段值中可以看到koa的入口文件为lib/application.js。对外暴露出一个类class Application。实例app中use、listen等方法就是定义在该类中。

    app.use()方法

    核心代码就两行,将fn函数push到middleware数组中,为了链式调用,return出实例。

    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 ' +
    'https://github.com/koajs/koa/blob/master/docs/migration.md');
    fn = convert(fn);
    }
    debug('use %s', fn._name || fn.name || '-');
    this.middleware.push(fn);
    return this;
    }

    app.listen方法

    与例1中的原生nodejs对比可知,this.callback()返回的是一个函数,相当于例1中的handleRequest方法。this指向koa实例app。

    1. 为什么不直接http.createServer(this.handleRequest)呢?这个是由于this的指向问题,这样做会导致handleRequest函数内部this指向Server实例,而不是app实例。
    1. 为什么不直接http.createServer(this.handleRequest.call(this))呢?这样无法拿到req和res参数。
      listen(...args) {
      debug('listen');
      const server = http.createServer(this.callback());
      return server.listen(...args);
      }

      app.callback方法

      这个方法中才包含了koa的核心实现,next开关实现koa中间件函数的控制。

      
      callback() {
      const fn = compose(this.middleware);
if (!this.listeners('error').length) this.on('error', this.onerror);

const handleRequest = (req, res) => {
  const ctx = this.createContext(req, res);
  return this.handleRequest(ctx, fn);
};

return handleRequest;

}

## 重写app.callback函数
为了提高对源码的理解,从零实现中间件的流程控制。再与koa源码中的实现对比优劣。以下代码监听到3000端口时,顺序打印出1 2 3 4 5,并响应回hello world。

const Koa = require('koa') const app = new Koa()

app .use(async (ctx, next) => { console.log(1) await next() console.log(5) }) .use(async (ctx, next) => { console.log(2) await next() console.log(4) }) .use(async (ctx, next) => { ctx.body = 'hello world' console.log(3) })

app.listen(3000);

## koa-compose模块
next的实现就是koa-compose模块中实现的。const fn = compose(this.middleware);
在node_modules中koa-compose源码

function compose (middleware) { if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!') for (const fn of middleware) { if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!') }

/**