什么是koa
Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。 通过利用 async 函数,Koa 帮你丢弃回调函数,并有力地增强错误处理。 Koa 并没有捆绑任何中间件, 而是提供了一套优雅的方法,帮助您快速而愉快地编写服务端应用程序。
koa的特点
koa2完全使用Promise并配合 async 来实现异步 其特点是
- 轻量
- 无捆绑
- 中间件架构
- 优雅的API设计
- 增强的错误处理
koa 的实现
构造一个 koa 类
class KOA {constructor(){// middlewares 数组用于管理中间件函数this.middlewares = []}listen(...args) {// 服务器监听端口实现逻辑}// 添加中间件函数方法use(middleware){this.middlewares.push(middleware)}createContext(req, res) {// 创建 context 上下文}compose(middlewares) {// 实现洋葱圈模型}}
创建一个应用程序:
const app = new Koa();
- KOA 类定义了 this.middlewares 数组来管理中间件函数;
- app.listen() 方法创建了一个 HTTP 服务器;
- app.use() 方法用于给 this.middlewares 添加中间件函数;
- app.createContext() 方法引入上下文context的概念,将原始请求对象req和响应对象res封装并挂载到 context上,并且在context上设置getter和setter,从而简化操作;
- app.compose() 方法则是洋葱圈模型的实现核心;
实现 listen 方法
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 方法
use 方法的作用就是给 this.middlewares 添加中间件函数
use(middleware){// 将中间件添加到数组里this.middlewares.push(middleware)}
实现 createContext 方法
createContext 主要是构建上下文对象,也就是中间件入参中的 ctx 对象,并为 context、request、response 对象挂载各种属性。 把res和req都挂载到ctx上,并且在ctx.req 和 ctx.request.req、ctx.res 和 ctx.response 上同时保存。
createContext(req, res) {const ctx = Object.create(context)ctx.request = Object.create(request)ctx.response = Object.create(response)ctx.req = ctx.request.req = reqctx.res = ctx.response.res = resreturn ctx}
实现 compose 方法
Koa中间件机制就是函数式组合概念 Compose 的概念,将一组需要顺序执行的函数复合为一个函数,外层函数的参数实际是内层函数的返回值。洋葱圈模型可以形象表示这种机制。
compose(middlewares) {// 传入上下文对象return function (ctx) {// 执行第一个中间件函数return dispatch(0)// dispatch函数递归遍历 middleware,// 然后将 context 和 dispatch(i + 1) 传给 middleware 中的方法function dispatch(i) {let fn = middlewares[i]if (!fn) {return Promise.resolve()}return Promise.resolve(// 将上下文传入中间件 mid(ctx, next)fn(ctx, function next() {// 执行下一个中间件函数return dispatch(i + 1)}))}}}
封装 request
request 是对 node 原生 request 的封装,使用 js 的 getter 和 setter 属性,将 node 原生 request 对象的一些方法封装到 koa 的 request 对象上。
// request.jsmodule.exports = {// 获取 urlget url() {return this.req.url;},// 获取 请求方法get method() {return this.req.method.toLowerCase()}};
代码中的 this.req 代表的是 node 的原生 request 对象,this.req.url 是 node 原生 request 中获取 url 的方法
封装 response
response 是对 node 原生 response 对象的封装,使用 js 的 getter 和 setter 属性,将 node 原生 response 对象的一些方法封装到 koa 的 response 对象上。
// response.jsmodule.exports = {get body() {return this._body;},set body(val) {this._body = val;},get status() {return this.res.statusCode;},set status(statusCode) {if (typeof statusCode !== 'number') {throw new Error('statusCode must be a number!')}this.res.statusCode = statusCode;}};
封装 context
// context.jsmodule.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}};
KOA 类的完整代码
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)}createContext(req, res) {const ctx = Object.create(context)ctx.request = Object.create(request)ctx.response = Object.create(response)ctx.req = ctx.request.req = reqctx.res = ctx.response.res = resreturn ctx}compose(middlewares) {return function (ctx) {return dispatch(0)function dispatch(i) {let fn = middlewares[i]if (!fn) {return Promise.resolve()}return Promise.resolve(fn(ctx, function next() {return dispatch(i + 1)}))}}}}module.exports = KOA;
koa 使用
const KOA = require('./koa')const app = new KOA()const delay = () => Promise.resolve(resolve => setTimeout(() => resolve(), 2000));app.use(async (ctx, next) => {ctx.body = "1";setTimeout(() => {ctx.body += "2";}, 2000);await next();ctx.body += "3";});app.use(async (ctx, next) => {ctx.body += "4";await delay();await next();ctx.body += "5";});app.use(async (ctx, next) => {ctx.body += "6";});
