Koa介绍与简单实现

391 阅读3分钟

这是我参与8月更文挑战的第11天,活动详情查看: 8月更文挑战”juejin.cn/post/698796…

基础介绍

概述

Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。

特点

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

安装

npm i koa -S

使用

const Koa = require('koa')
// 创建app实例
const app = new Koa();

// 中间件
app.use(async (ctx,next)=>{
  ctx.body = "hello world"
})

// 监听端口
app.listen(3000)

原理分析

书读千遍,不如手抄一遍。分析框架的最好办法,自然莫过于动手实现一遍。

主要入口

根据Koa框架的基础使用代码,可以分析出Koa类中主要包含use() 方法和 listen() 方法

// index.js
const http = require("http");
class Koa{
  listen(...args){
    const server = http.createServer((req,res)=>{
      this.callback(req,res)
    })
    server.listen(...args)
  }
  use(callback){
    this.callback = callback;
  }
}

Context对象

Koa为了简化API,引入了上下文Context概念。将原始的请求对象 req 和响应对象 res 封装并挂载到了 context 上,并且设置了 gettersetter ,从而简化操作

// 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
  }
}
// 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
  }
}
// index.js
const http = require("http");
const context = require('./context')
const request= require('./request')
const response= require('./response')
class Koa{
  listen(...args){
    const server = http.createServer((req,res)=>{
      // 创建上下文对象
      let ctx = this.createContext(req,res)
      this.callback(ctx)
      res.end(ctx.body)
    })
    server.listen(...args)
  }
  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
  }
  use(callback){
    this.callback = callback;
  }
}

在这里可能有些同学会看的比较懵,ctx.request、ctx.req、ctx.response、ctx.res 看起来都很相似,但具体有什么区别呢?

  • ctx.request Koa的Request对象
  • ctx.response Koa的Response对象
  • ctx.req Node的request 对象
  • ctx.res Node的response对象

中间件

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

126473-55e70246bb1e8bb7.webp

同步函数组合

const compose = (...[first,...other]) => (...args)=>{
  let ret = first(...args)
  other.forEach(fn=>{
    ret = fn(ret)
  })
  return ret
}
const add = (x, y) => x + y
const square = z => z * z

const fn = compose(add,square)
fn(1,2) // 9 = (1+2) * 3

异步函数组合

在中间件中,大部分的应用场景都是异步的。所以需要将同步函数组合改造为异步函数组合的形式

function compose(middlewares){
  return function(){
    return dispatch(0)
    
    function dispatch(i){
      let 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 new Promise((reslove, reject) => {
        setTimeout(() => { 
          reslove();
        }, 2000);
    });
}
const middlewares = [fn1, fn2, fn3];
const finalFn = compose(middlewares);
finalFn();

fn1
fn2
fn3
end fn2
end fn1

入口改造

// index.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((req,res)=>{
      // 创建上下文对象
      let ctx = this.createContext(req,res)
      // 合成中间件
      const fn = this.compose(this.middlewares)
      await fn(ctx)
      res.end(ctx.body)
    })
    server.listen(...args)
  }
  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
  }
  use(middleware){
    this.middlewares.push(middleware)
  }
  function 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)
          }) 
        )
      }
    }
  }
}

测试成果

// index.js
const delay = () => new Promise(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";
});

正确答案为:12345