前言

  • 找到koa源码的package.json文件,找到”main”: “lib/application.js”,此时我们调用的Koa的类就是在application.js中的
  • application主要做的事情就是实例化应用,
  • context主要做的事情就是实例上下文,
  • request.js由原生request事件的http.IncomingMessage类过滤而来;
  • response.js对应ctx.response,由原生request事件的http.ServerResponse类过滤而来。

里面的Application类实现了koa的各个方法

处理中间件

  • 我们调用中间件的时候传入的callback,callback循环被组合的中间件,递归调用中间件 ```javascript /**

    • Shorthand for: *
    • http.createServer(app.callback()).listen(…) *
    • @param {Mixed} …
    • @return {Server}
    • @api public */

    listen(…args) { debug(‘listen’); const server = http.createServer(this.callback()); return server.listen(…args); }

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

  1. if (!this.listenerCount('error')) this.on('error', this.onerror);
  2. const handleRequest = (req, res) => {
  3. const ctx = this.createContext(req, res);
  4. return this.handleRequest(ctx, fn);
  5. };
  6. return handleRequest;

}

<a name="f7n8H"></a>
#### context->ctx别名处理

- koa内部使用的是proto.__defineSetter__和proto.__defineGetter__ [__defineGetter__(mdn已废弃)](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/__defineGetter__)
- 也可以使用Object.defineProperty
```javascript
const context = {
    // get method() {
    //     return this.request.method
    // },

    // get url(){
    //   return this.request.url
    // }
}

defineProperty('request', 'method')
defineProperty('request', 'url')
defineProperty('request', 'body')

function defineProperty(target, name) {
    // context.__defineGetter__(name, function () {
    //     return this[target][name]
    // })
    Object.defineProperty(context, name, {
        get() {
            return this[target][name]
        },
        set(value) {
            this[target][name] = value
        }
    })
}
module.exports = context

简单的实现

application.js

  • 洋葱结构,当初为什么要这样设计呢?因为是为了解决复杂应用中频繁的回调而设计的级联代码,它并不会把控制权完全交给一个中间件的代码,而是碰到next就会去下一个中间件,等下面所有中间件执行完成后,就会再回来执行中间件未完成的代码。

    为什么要返回promise呢
  • 所以Promise.resolve的作用, 在普通函数中依旧能使用next().then()的形式,保证中间件的正确运行

  • 如果所有的中间件都为普通函数并且没有异步,洋葱模型的实现与调用栈有关,

由于async,await的特性,所有的中间件都使用async/await 也可以保持异步的情况下保持洋葱模型,
此时都可以使用compose-sync 达到与compose-async同样的效果
但当要在普通函数中实现异步保持洋葱模型,则需要compose-async 返回promise, 以达到与async/await 同样的效果。

const http = require('http')
const context = require('./context')
const request = require('./request')
const response = require('./response')

class Application {
    constructor() {
        // 保存用户添加的中间件函数
        this.middleware = []
        // 为了方便实用,直接添加到application实例上
        // 如果有多个实例共享的时候,数据容易受到污染
        this.context = Object.create(context)
        this.request = Object.create(request)
        this.response = Object.create(response)
    }
    listen(...args) {
        // 把相关逻辑抽离出去
        const server = http.createServer(this.callback())
        server.listen(...args)
    }

    // 当调用use的时候,调用的处理函数(中间件),往middleware中push中间件
    use(fn) {
        this.middleware.push(fn)
    }

    // 异步递归遍历调用中间件处理函数 next
    compose(middleware) {
        return function (context) {
            const dispatch = index => {
                // next就是dispatch,处理index边界问题
                // 整个返回的都是promise,所以在超出边界的时候也要返回promise.resolve成功的状态
                if (index >= middleware.length) return Promise.resolve()
                const fn = middleware[index]
                // 把fn强制的包装到一个promise里面
                return Promise.resolve(
                    // 上下文对象
                    fn(context, () => dispatch(index + 1)) // 这是next函数
                )
            }
            // 返回第一个中间件处理函数
            // dispatch不是一数组。是一个函数,调用dispatch
            return dispatch(0)
        }
    }

    // 创建上下文对象的函数
    // 构造上文对象
    createContext(req, res) {
        // 一个实例会处理多个请求,而不同的请求应该拥有不同的上下文对象,为了避免请求期间的数据交叉污染
        // 所以这里又对数据做了一份新的拷贝
        const 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;
        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;
        console.log(req);
        context.originalUrl = request.originalUrl = req.url;
        context.state = {};
        return context;
    }

    callback() {
        // compose进行递归遍历,把当前存储中间件函数的数组存储进来
        // 扩展性更好,middleware可以通过参数传进来
        const fnMiddleware = this.compose(this.middleware)
        const handleRequest = (req, res) => {
            // 最终对应的结果
            // 每个请求都会创建一个独立的context对象,他们之间不会互相污染
            const context = this.createContext()
            fnMiddleware(context).then(() => {
                // console.log('end');
                res.end(context.body)
                // res.end('My Koa')
            }).catch(err => {
                res.end(err.message)
            })
        }
        return handleRequest
    }
}

module.exports = Application

context

const context = {
    // get method() {
    //     return this.request.method
    // },

    // get url(){
    //   return this.request.url
    // }
}

defineProperty('request', 'method')
defineProperty('request', 'url')
defineProperty('request', 'body')

function defineProperty(target, name) {
    // context.__defineGetter__(name, function () {
    //     return this[target][name]
    // })
    Object.defineProperty(context, name, {
        get() {
            return this[target][name]
        },
        set(value) {
            this[target][name] = value
        }
    })
}
module.exports = context

response

const response = {
    set status(val) {
        this.res.statusCode = val
    },

    _body: '', // 真正用来存储数据的
    get body() {
        return this._body
    },
    set body(value) {
        this._body = value
    }
}
module.exports = response

request

const url = require('url')
const request = {
    // 对象属性访问器
    get method() {
        console.log(this);
        return this.req.method
    },

    get header() {
        return this.req.headers
    },

    get url() {
        return this.req.url
    },

    get path() {
        return url.parse(this.req.url).pathname
    },

    get query() {
        return url.parse(this.req.url, true).query
    }
}
module.exports = request

推荐大佬文章

若川大佬详解koa源码