什么是koa

Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。 通过利用 async 函数,Koa 帮你丢弃回调函数,并有力地增强错误处理。 Koa 并没有捆绑任何中间件, 而是提供了一套优雅的方法,帮助您快速而愉快地编写服务端应用程序。

koa的特点

koa2完全使用Promise并配合 async 来实现异步 其特点是

  • 轻量
  • 无捆绑
  • 中间件架构
  • 优雅的API设计
  • 增强的错误处理

koa 的实现

构造一个 koa 类

  1. class KOA {
  2. constructor(){
  3. // middlewares 数组用于管理中间件函数
  4. this.middlewares = []
  5. }
  6. listen(...args) {
  7. // 服务器监听端口实现逻辑
  8. }
  9. // 添加中间件函数方法
  10. use(middleware){
  11. this.middlewares.push(middleware)
  12. }
  13. createContext(req, res) {
  14. // 创建 context 上下文
  15. }
  16. compose(middlewares) {
  17. // 实现洋葱圈模型
  18. }
  19. }

创建一个应用程序:

  1. 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 方法

  1. listen(...args) {
  2. const server = http.createServer(async (req, res) => {
  3. // 创建上下文对象
  4. const ctx = this.createContext(req, res);
  5. // 中间件合成
  6. const fn = this.compose(this.middlewares);
  7. // 执行合成函数并传入上下文
  8. await fn(ctx);
  9. res.end(ctx.body);
  10. })
  11. // 监听端口
  12. server.listen(...args)
  13. }

实现 use 方法

use 方法的作用就是给 this.middlewares 添加中间件函数

  1. use(middleware){
  2. // 将中间件添加到数组里
  3. this.middlewares.push(middleware)
  4. }

实现 createContext 方法

createContext 主要是构建上下文对象,也就是中间件入参中的 ctx 对象,并为 context、request、response 对象挂载各种属性。 把res和req都挂载到ctx上,并且在ctx.req 和 ctx.request.req、ctx.res 和 ctx.response 上同时保存。

  1. createContext(req, res) {
  2. const ctx = Object.create(context)
  3. ctx.request = Object.create(request)
  4. ctx.response = Object.create(response)
  5. ctx.req = ctx.request.req = req
  6. ctx.res = ctx.response.res = res
  7. return ctx
  8. }

实现 compose 方法

Koa中间件机制就是函数式组合概念 Compose 的概念,将一组需要顺序执行的函数复合为一个函数,外层函数的参数实际是内层函数的返回值。洋葱圈模型可以形象表示这种机制。
image.png

  1. compose(middlewares) {
  2. // 传入上下文对象
  3. return function (ctx) {
  4. // 执行第一个中间件函数
  5. return dispatch(0)
  6. // dispatch函数递归遍历 middleware,
  7. // 然后将 context 和 dispatch(i + 1) 传给 middleware 中的方法
  8. function dispatch(i) {
  9. let fn = middlewares[i]
  10. if (!fn) {
  11. return Promise.resolve()
  12. }
  13. return Promise.resolve(
  14. // 将上下文传入中间件 mid(ctx, next)
  15. fn(ctx, function next() {
  16. // 执行下一个中间件函数
  17. return dispatch(i + 1)
  18. })
  19. )
  20. }
  21. }
  22. }

封装 request

request 是对 node 原生 request 的封装,使用 js 的 getter 和 setter 属性,将 node 原生 request 对象的一些方法封装到 koa 的 request 对象上。

  1. // request.js
  2. module.exports = {
  3. // 获取 url
  4. get url() {
  5. return this.req.url;
  6. },
  7. // 获取 请求方法
  8. get method() {
  9. return this.req.method.toLowerCase()
  10. }
  11. };

代码中的 this.req 代表的是 node 的原生 request 对象,this.req.url 是 node 原生 request 中获取 url 的方法

封装 response

response 是对 node 原生 response 对象的封装,使用 js 的 getter 和 setter 属性,将 node 原生 response 对象的一些方法封装到 koa 的 response 对象上。

  1. // response.js
  2. module.exports = {
  3. get body() {
  4. return this._body;
  5. },
  6. set body(val) {
  7. this._body = val;
  8. },
  9. get status() {
  10. return this.res.statusCode;
  11. },
  12. set status(statusCode) {
  13. if (typeof statusCode !== 'number') {
  14. throw new Error('statusCode must be a number!')
  15. }
  16. this.res.statusCode = statusCode;
  17. }
  18. };

封装 context

  1. // context.js
  2. module.exports = {
  3. get url() {
  4. return this.request.url;
  5. },
  6. get body() {
  7. return this.response.body;
  8. },
  9. set body(val) {
  10. this.response.body = val;
  11. },
  12. get method() {
  13. return this.request.method
  14. }
  15. };

KOA 类的完整代码

  1. const http = require('http')
  2. const context = require('./context')
  3. const request = require('./request')
  4. const response = require('./response')
  5. class KOA {
  6. constructor(){
  7. this.middlewares = []
  8. }
  9. listen(...args) {
  10. const server = http.createServer(async (req, res) => {
  11. // 创建上下文
  12. const ctx = this.createContext(req, res)
  13. const fn = this.compose(this.middlewares)
  14. await fn(ctx)
  15. res.end(ctx.body)
  16. })
  17. server.listen(...args)
  18. }
  19. use(middleware){
  20. this.middlewares.push(middleware)
  21. }
  22. createContext(req, res) {
  23. const ctx = Object.create(context)
  24. ctx.request = Object.create(request)
  25. ctx.response = Object.create(response)
  26. ctx.req = ctx.request.req = req
  27. ctx.res = ctx.response.res = res
  28. return ctx
  29. }
  30. compose(middlewares) {
  31. return function (ctx) {
  32. return dispatch(0)
  33. function dispatch(i) {
  34. let fn = middlewares[i]
  35. if (!fn) {
  36. return Promise.resolve()
  37. }
  38. return Promise.resolve(
  39. fn(ctx, function next() {
  40. return dispatch(i + 1)
  41. })
  42. )
  43. }
  44. }
  45. }
  46. }
  47. module.exports = KOA;

koa 使用

  1. const KOA = require('./koa')
  2. const app = new KOA()
  3. const delay = () => Promise.resolve(resolve => setTimeout(() => resolve(), 2000));
  4. app.use(async (ctx, next) => {
  5. ctx.body = "1";
  6. setTimeout(() => {
  7. ctx.body += "2";
  8. }, 2000);
  9. await next();
  10. ctx.body += "3";
  11. });
  12. app.use(async (ctx, next) => {
  13. ctx.body += "4";
  14. await delay();
  15. await next();
  16. ctx.body += "5";
  17. });
  18. app.use(async (ctx, next) => {
  19. ctx.body += "6";
  20. });