精簡版 koa 簡單實現

qianyin925發表於2018-12-08

一、 Application 模組的簡單封裝

首先我們先簡單封裝一個模組 Application 保證服務的正常執行;

  • 初始化一個專案
$ npm init -y
...
複製程式碼
  • 建立檔案 application.js 並並編寫如下程式碼;
const http = require('http');

class Application{
  // 初始化
  constructor(){
    this.callback = () => {}
  }

  // 設定回撥函式
  use(callback){
    this.callback = callback;
  }

  // listen 建立服務並對服務進行監聽
  listen(...args){
    const server = http.createServer((req, res) => {
      this.callback(req, res);
    });
    server.listen(...args);
  }
}
module.exports = Application;
複製程式碼
  • 建立 server.js 檔案,呼叫 Application 模組起一個服務:
const Application = require('./application.js');

const app = new Application();

app.use((req, res) => {
  res.writeHead(200);
  res.end('hello world');
});

app.listen(3000, () => {
  console.log('監聽埠:3000');
});

複製程式碼

二、 Application 模組掛載 context

首先我們假設我們的 context 是這麼一個資料結構:

  • context 中掛載了 request response req res, 同時還有抽離的額外屬性 url body
  • request 中掛載了 req, 同時還有抽離的額外屬性 url
  • response 中掛載了 res, 同時還有抽離的額外屬性 body
context: {
  url: String,
  body: String,
  request: {
    url: String,
    req: Object
  },
  response: {
    body: String,
    res: Object
  },
  req: Object,
  res: Object
}
複製程式碼

改寫 Application

  • 設計 context request response 原型資料結構;
  • 將 context request response 原型資料結構掛載到 Application
  • 編寫函式建立 context
  • 改寫回撥函式的呼叫方式;
const http = require('http');
// [1]構建資料結構(作為原型使用)
const request = {
  // 因為後期 request 將會掛載上 req 所以存在 this.req
  get url(){
    return this.req.url;
  }
};
const response = {
  get body(){
    return this._body;
  },
  set body(val){
    this._body = val;
  }
};
const context = {
  // 因為後期 context 將會掛載上 request response 所以存在 this.request 和  this.response
  get url(){
    return this.request.url;
  },
  get body(){
    return this.response.body;
  },
  set body(val){
    this.response.body = val; 
  }
}


class Application{
  constructor(){
    this.callback = () => {},
    // [2]將原型掛載到 Application
    this.context = context;
    this.request = request;
    this.response = response;
  }

  use(callback){
    this.callback = callback;
  }
  // [3]建立 context 函式,掛載上 request response req res 
  createCtx(req, res){
    const ctx = Object.create(this.context);
    ctx.request = Object.create(this.request);
    ctx.response = Object.create(this.response);
    ctx.req = ctx.request = req;
    ctx.res = ctx.response = res;
    return ctx;
  }

  listen(...args){
    const server = http.createServer((req, res) => {
      // [4]建立 context, 並進行簡單修改
      const ctx = this.createCtx(req, res);
      this.callback(ctx);
      ctx.res.end(ctx.body);
    });
    server.listen(...args);
  }
}
module.exports = Application;

複製程式碼

修改 server.js 中 Application 的引用

const Application = require('./application.js');

const app = new Application();

app.use( ctx => {
  ctx.body = 'hello world'
});

app.listen(3000, () => {
  console.log('監聽埠:3000');
});
複製程式碼

三、 中介軟體的實現

3.1 洋蔥模型實現

// 場景模擬
// 非同步 promise 模擬
const delay = async () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      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);

複製程式碼

3.2 compose 函式在 Application 模組中的使用:

const http = require('http');

const request = {
  get url(){
    return this.req.url;
  }
};
const response = {
  get body(){
    return this._body;
  },
  set body(val){
    this._body = val;
  }
};
const context = {
  get url(){
    return this.request.url;
  },
  get body(){
    return this.response.body;
  },
  set body(val){
    this.response.body = val; 
  }
}


class Application{
  constructor(){
    this.context = context;
    this.request = request;
    this.response = response;
    // 初始化中介軟體陣列
    this.middlewares = [];
  }

  // 通過push的方式進行新增中介軟體
  use(middleware){
    this.middlewares.push(middleware);
  }

  createCtx(req, res){
    const ctx = Object.create(this.context);
    ctx.request = Object.create(this.request);
    ctx.response = Object.create(this.response);
    ctx.req = ctx.request = req;
    ctx.res = ctx.response = res;
    return ctx;
  }
  // compose 函式
  compose(middlewares, ctx){
    const dispatch = (i) => {
      const fn = middlewares[i];
      if(!fn){
        return Promise.resolve();
      }else{
        return Promise.resolve(fn(ctx, () => {
          dispatch(i +1 );
        }));
      }
    }

    return dispatch(0);
  }

  listen(...args){
    // 改用 async await 並呼叫compose
    const server = http.createServer(async (req, res) => {
      const ctx = this.createCtx(req, res);
      await this.compose(this.middlewares, ctx);
      ctx.res.end(ctx.body);
    });
    server.listen(...args);
  }
}
module.exports = Application;

複製程式碼

相關文章