为什么需要Koa,对比原始Node的HTTP

源码

大家好,我是有用就扩散,有用就点赞。

为什么需要Koa,对比HTTP

1)路由麻烦,if else过多(可以使用策略模式)

2)重复代码有点多,比如statusCode的赋值

3)请求解析与响应体包装,原始代码过于臃肿

4)请求的解析源代码太多,API不优雅

5)AOP的支持(面向切面编程),引入洋葱模型

洋葱圈模型设计有以下几点好处:

  • 更好地封装和复用代码逻辑,每个中间件只需要关注自己的功能;
  • 更清晰的程序逻辑,通过中间件的嵌套可以表明代码的执行顺序;
  • 更好的错误处理,每个中间件可以选择捕获错误或将错误传递给外层;
  • 更高的扩展性,可以很容易地在中间件栈中添加或删除中间件。

Koa源码初步实现

1.洋葱模型,use方法的组合

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

class Koa {
    constructor() {
        this.middlewares = []
    }
    listen(...args) {
        const server = http.createServer(async (req, res) => {
            // 创建上下文
            const ctx = this.createContext(req, res)
            const fn = this.compose(this.middlewares)
            await fn(ctx)
            // 返回响应
            res.end(ctx.body)
        })
        server.listen(...args)
    }
    use(middleware) {
        this.middlewares.push(middleware)
    }

    // 通过getter和setter来拦截request和response,实现优雅设置body
    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
    }

    // 实现koa的compose
    compose(middlewares) {
        return function (ctx) {
            return dispatch(0)
            function dispatch(i) {
                const fn = middlewares[i]
                if (!fn) return Promise.resolve()
                return Promise.resolve(fn(ctx, function next() {
                    return dispatch(i + 1)
                }))
            }
        }
    }
}

module.exports = Koa
// 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
    }
}
// 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
    }
}

实现

// app.js
const Koa = require('./koa');
const app = new Koa();

// 洋葱模型,use方法的组合
const delay = () => Promise.resolve(resolve => setTimeout(() => resolve(), 2000));

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

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

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

app.listen(3000, () => {
    console.log("listening on 3000");
})

效果

在这里插入图片描述

2.路由策略模式

// router.js
class Router {
    constructor() {
      this.stack = [];
    }
  
    register(path, methods, middleware) {
      let route = {path, methods, middleware}
      this.stack.push(route);
    }
    // 现在只支持get和post,其他的同理
    get(path,middleware){
      this.register(path, 'get', middleware);
    }
    post(path,middleware){
      this.register(path, 'post', middleware);
    }
    routes() {
      let stock = this.stack;
      return async function(ctx, next) {
        let currentPath = ctx.url;
        let route;
  
        for (let i = 0; i < stock.length; i++) {
          let item = stock[i];
          if (currentPath === item.path && item.methods.indexOf(ctx.method) >= 0) {
            // 判断path和method
            route = item.middleware;
            break;
          }
        }
  
        if (typeof route === 'function') {
          route(ctx, next);
          return;
        }
  
        await next();
      };
    }
  }
  module.exports = Router;

实现

// app.js
const Koa = require('./koa');
const app = new Koa();

const Router = require('./router')
const router = new Router()

router.get('/index', async ctx => { ctx.body = 'index page'; });
router.get('/post', async ctx => { ctx.body = 'post page'; });
router.get('/list', async ctx => { ctx.body = 'list page'; });
router.post('/index', async ctx => { ctx.body = 'post page'; });
// 路由实例输出父中间件 router.routes()
app.use(router.routes());

app.listen(3000, () => {
    console.log("listening on 3000");
})

效果

在这里插入图片描述

3.组合的方法的几种实现方式

// middlewares.js
const add = (x, y) => x + y
const square = z => z * z

// 方法一 简单实现
//  const fn = (x, y) => square(add(x, y))

// 方法二 简单组合封装
// const compose = (fn1, fn2) => (...args) => fn2(fn1(...args))
// const fn = compose(add, square)

// 方法三 实现多个方法组合
// const compose = (...[first,...other]) => (...args) => {
//     let ret = first(...args)
//     other.forEach(fn => {
//         ret = fn(ret)
//     })
//     return ret
// }
// const fn = compose(add, square)

// 方法四 考虑中间件执行顺序,实现多个方法组合,同时考虑异步
function compose(middlewares) {
    return function () {
        return dispatch(0)
        function dispatch(i) {
            const fn = middlewares[i]
            if (!fn) return Promise.resolve()
            return Promise.resolve(fn(function next() {
                return dispatch(i + 1)
            }))
        }
    }
}
// 方法四实现例子
async function fn1(next) {
    console.log('fn1')
    await next()
    console.log('end fn1')
}

async function fn2(next) {
    console.log('fn2')
    await delay()
    await next()
    console.log('end fn2')
}

function fn3(next) {
    console.log('fn3')
}

function delay() {
    return Promise.resolve(res => {
        setTimeout(() => reslove(), 2000)
    })
}
const middlewares = [fn1, fn2, fn3]
const finalFn = compose(middlewares)
finalFn()

源码

nction fn3(next) {
console.log(‘fn3’)
}

function delay() {
return Promise.resolve(res => {
setTimeout(() => reslove(), 2000)
})
}
const middlewares = [fn1, fn2, fn3]
const finalFn = compose(middlewares)
finalFn()


[**源码**](https://github.com/XYuanZ/Web-study)

欢迎各位大哥投稿 PR
  • 15
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值