从零实现一个koa(二)实现koa中间件

1,417 阅读3分钟

使用说明

  • 我们知道当我们得到koa的实例之后,我们可以使用app.use去给koa注册一个中间件。然后当我们有对服务器进来之后就会触发我们的中间件。其中中间件有两个参数ctx代表请求的上下文,这个我们会在下一篇文章做解析。next将执行权交给下一个中间件,按次序执行所有的中间件,如果不写,那么之后的中间件将无法得到执行。
conset Koa = require('koa');
const app = new Koa()
app.use(async (ctx,next) => {
    console.log(ctx, '这里是请求的上下文里边包括了req,res等等代理到ctx上')
    ctx.status = 200
    ctx.body = '实例'
    await next();
})

解析app.use

  • app.use用于添加中间件函数,且可以链式添加。并且添加之后当请求到来,顺序调用。那么我们可以用一个队列来存储中间件函数并且每个中间件函数返回this即可
const Emitter = require('events');
module.exports = class Koa extends(Emitter) {
    constructor() {
        super()
        this.middleware = [];
    }
    use(fn) {
        this.middleware.push(fn)
        return this;
    }
}

处理http请求顺序执行中间件函数

在我们保存中间件函数到队列middleware中之后,我们就要解析请求到来,顺序执行middleware中的函数了。还记得我们上一章的callback吗?我们的逻辑处理在这里。

const http = require('http');
const Emitter = require('events');
module.exports = class Koa extends(Emitter) {
    constructor() {
        super()
        this.middleware = [];
    }
    use(fn) {
        this.middleware.push(fn)
        return this;
    }
    listen(...arg) { 
        const server = http.createServer(this.callback); 
        return server.listen(...arg) 
    }
    callback() {
        const fn = compose(this.middleware);
        function handleRequest(req,res) {
            const ctx = {req,res}
            return this.handleRequest(ctx, fn)
        }
    }
    handleRequest(ctx, fn) {
        return fn(ctx).then(console.log('success')).catch(err => {console.log(err)})
    }
}

compose函数在这里比较重要,是关键逻辑。他的作用将中间件函数队列组合成一个函数并且可以异步顺序执行。我们来看下实现步骤

  • 1:定义函数以及错误兼容处理。
function compose(middleware) {
    if (!Array.isArray(middleware)) {
        throw new TypeError('middleware must be an array!')
    }
    for (const fn of middleware) {
        if (typeof fn !== 'function') {
            throw new TypeError('Middleware must be composed of functions!')
        }
    }
    return function(context, next) {

    }
}
  • 2:实现返回函数,处理middleware。从功能上我们要实现调用一个中间件函数,然后通过执行next参数,将执行权交给下一个中间件函数。
return function (context, next) {
   function dispatch(index) {
       if (index >= middleware.length) {
            console.log('执行完毕!')
            return 
       }
       middleware[index](context, dispatch.bind(null, index+=1))
   }
}
  • 3: 处理middleware中参数为async函数的情况,我们知道Promise.resolve中的值为promise时候,会把promise的状态当做Promise.resolve().then的状态。我们这里又是async函数。我们await next(),next返回一个promise,因此会一直遍历调用下去。直到遍历完所有队列中的中间件函数。
return function (context, next) {
   function dispatch(index) {
       if (index >= middleware.length) {
            console.log('执行完毕!')
            // 中间件数组执行完毕,返回一个fullfilled的promise。
            return Promise.resolve()
       }
       let fn = middleware[index]
       try {
        Promise.resolve(fn(context,dispatch.bind(null, index+=1)))
       } catch(err) {
        return Promise.reject(err)
       }
   }
}
  • 4:处理中间件函数中多次调用next的问题。我们用一个变量pre存储上初次调用的是第几个,那么本次执行dispatch势必index大于pre。当某个中间件多次调用时候,势必发生传入这个dispatch中的index小于pre。
return function (context, next) {
   let pre = -1
   function dispatch(index) {
       if (pre >= index) {
           throw new Error('next() called multiple times')
       }
       pre = index
       if (index >= middleware.length) {
            console.log('执行完毕!')
            // 中间件数组执行完毕,返回一个fullfilled的promise。
            return Promise.resolve()
       }
       let current = -1
       let fn = middleware[index]
       try {
        Promise.resolve(fn(context,dispatch.bind(null, index+=1)))
       } catch(err) {
        return Promise.reject(err)
       }
   }
}

到了这里我们的compose函数已经实现完成,接下来我们将实现ctx。以及补充完成我们的koa,并且进行测试。