KoaJS

2022-07-14 17:41:04 浏览数 (2)

前言

koa致力于成为一个更小、更富有表现力、更健壮的、更轻量的web开发框架。因为它所有功能都通过插件实现,这种插拔式的架构设计模式,很符合unix哲学。

一个简单的服务,如下:

代码语言:javascript复制
const Koa = require('koa') 
let app = new Koa() 
app.use((ctx, next) => { 
    console.log(ctx) 
}) 
app.listen(4000)
复制代码

然后在浏览器端打开http://127.0.0.1:4000即可访问

若没有指定返回body,koa默认处理成了Not Found

本文内容:

  • 中间件原理(结合代码)
    • 原理
    • 中间件实现思路
    • 理解上述洋葱模型
  • 阅读源码
    • app.listen()
    • ctx挂载内容
      • context.js
      • request.js
      • response.js
      • 挂载ctx
    • next构建的洋葱模型
      • app.use((ctx, next) =< { ... })
      • 中间件含异步代码如何保证正确执行
      • 返回报文
      • 解决多次调用next导致混乱问题
    • 基于事件驱动去处理异常
  • koa2, koa1 和 express区别

一、中间件原理(结合代码)

原理

  • 中间件执行就像穿越洋葱一样,最早use的中间件,就放在最外层。处理顺序横穿洋葱,从左到右,左边接收一个request,右边输出返回response;
  • 一般的中间件都会执行两次,调用next之前为第一次,调用next时把控制传递给下游的下一个中间件。当下游不再有中间件或者没有执行next函数时,就将依次恢复上游中间件的行为,让上游中间件执行next之后的代码;

如下代码:

代码语言:javascript复制
const Koa = require('koa')
const app = new Koa()
app.use((ctx, next) => {
    console.log(1)
    next()
    console.log(3)
})
app.use((ctx) => {
    console.log(2)
})
app.listen(9001)

# 执行结果是1=>2=>3
复制代码

中间件实现思路

代码语言:javascript复制
# 注意其中的compose函数,这个函数是实现中间件洋葱模型的关键
// 场景模拟
// 异步 promise 模拟
const delay = async () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('delay 2000ms')
      resolve();
    }, 2000);
  });
}
// 中间间模拟
const fn1 = async (ctx, next) => {
  console.log(1);
  await next();
  console.log(2);
}
const fn2 = async (ctx, next) => {
  console.log(3);
  await delay();
  await next();
  console.log(4);
}
const fn3 = async (ctx, next) => {
  console.log(5);
}

const middlewares = [fn1, fn2, fn3];

// compose 实现洋葱模型
const compose = (middlewares, ctx) => {
  const dispatch = (i) => {
    let fn = middlewares[i];
    if(!fn){ return Promise.resolve() }
    return Promise.resolve(fn(ctx, () => {
      return dispatch(i 1);
    }));
  }
  return dispatch(0);
}

compose(middlewares, 1);
复制代码

理解上述洋葱模型

代码语言:javascript复制
const fn1 = async (ctx, next) => { 
    console.log(1); 
    
    const fn2 = async (ctx, next) => { 
        console.log(3); 
        await delay(); 

        const fn3 = async (ctx, next) => { 
            console.log(5); 
        }

        console.log(4); 
    }
    
    console.log(2); 
}

# 1 3 5 4 2
复制代码

看完这个,大概了解koa的中间件原理了吧。 接下来,咱们一起看下源码。

二、阅读源码

核心文件四个

  • application.js:简单封装http.createServer()并整合context.js application.js是koa的入口文件,它向外导出了创建class实例的构造函数, 它继承了events,这样就会赋予框架事件监听和事件触发的能力。 application还暴露了一些常用的api,比如toJSON、listen、use等等。 listen的实现原理其实就是对http.createServer进行了一个封装, 重点是这个函数中传入的callback, 它里面包含了中间件的合并,上下文的处理,对res的特殊处理。 use是收集中间件,将多个中间件放入一个缓存队列中, 然后通过koa-compose这个插件进行递归组合调用这一些列的中间件。 复制代码
  • context.js:代理并整合request.jsresponse.js
  • request.js:基于原生req封装的更好用
  • response.js:基于原生res封装的更好用

koa是用ES6实现的,主要是两个核心方法app.listen()app.use((ctx, next) => { ... })

1. app.listen()

application.js中实现 app.listen()

handleRequest()

代码语言:javascript复制
# application.js

    const http = require('http')
    class Koa {
      constructor () {
        // ...
      }  
        // 处理用户请求
      handleRequest (req, res) {
        // req & res nodejs native
        // ...
      }  
      listen (...args) {
        let server = http.createServer(this.handleRequest.bind(this))
        server.listen(...args)
      }  
    }
    module.exports = Koa
复制代码

2. ctx挂载内容

代码语言:javascript复制
ctx = {}
ctx.request = {}
ctx.response = {}
ctx.req = ctx.request.req = req
ctx.res = ctx.response.res = res
ctx.xxx = ctx.request.xxx
ctx.yyy = ctx.response.yyy
复制代码

我们需要以上几个对象,最终都代理到ctx对象上。

创建context.js/request.js/response.js三个文件


2.1 request.js内容

代码语言:javascript复制
# request.js

const url = require('url')
let request = {}
module.exports = request
复制代码

request.js中,使用ES5提供的属性访问器实现封装

代码语言:javascript复制
# request.js

const url = require('url')
let request = {
  get url () {
    return this.req.url // 此时的this为调用的对象 ctx.request
  },
  get path () {
    let { pathname } = url.parse(this.req.url)
    return pathname
  },
  get query () {
    let { query } = url.parse(this.req.url, true)
    return query
  }
  // ...更多待完善
}
module.exports = request
复制代码

以上实现了封装request并代理到ctx


2.2 response.js内容

代码语言:javascript复制
# response.js

let response = {}
module.exports = response
复制代码

response.js中,使用ES5提供的属性访问器实现封装

代码语言:javascript复制
# response.js

let response = {
  set body (val) {
    this._body = val
  },
  get body () {
    return this._body // 此时的this为调用的对象 ctx.response
  }
  // ...更多待完善
}
module.exports = response
复制代码

以上实现了封装response并代理到ctx


2.3 context.js内容

代码语言:javascript复制
# context.js 初始化

let context = {}

module.exports = context
复制代码

context.js中,使用__defineGetter__ / __defineSetter__实现代理,他是Object.defineProperty()方法的变种,可以单独设置get/set,不会覆盖设置。

代码语言:javascript复制
# context.js

let context = {}
// 定义获取器
function defineGetter (key, property) {
  context.__defineGetter__ (property, function () {
    return this[key][property]
  })
}
// 定义设置器
function defineSetter (key, property) {
  context.__defineSetter__ (property, function (val) {
    this[key][property] = val
  })
}

// 


	

0 人点赞