Koa 中間件洋蔥圈模型和 Express 的中間件模型比對


express

先來一段 express 代碼

// app.js

var express = require('express');
var path = require('path');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');
app.use(express.static(path.join(__dirname, 'public')));

app.use('/test', function(req, res, next) {
  console.log(req.headers);
  console.log(req.body);
  console.log(req.query);
  res.header({
    'Content-type': 'application/json'
  });
  res.json({
    success: true
  });
});

module.exports = app;

其 app.use 就只是把回調函數放進棧里,用 Layer 包裹,Layer 結構

function Layer(path, options, fn) {
  if (!(this instanceof Layer)) {
    return new Layer(path, options, fn);
  }

  debug('new %o', path)
  var opts = options || {};
  // 處理的回調函數放這里
  this.handle = fn;
  this.name = fn.name || '<anonymous>';
  this.params = undefined;
  this.path = undefined;
  this.regexp = pathRegexp(path, this.keys = [], opts);

  // set fast path flags
  this.regexp.fast_star = path === '*'
  this.regexp.fast_slash = path === '/' && opts.end === false
}

調用時執行中間件的 handle,中間件里再手動執行 next 來鏈式執行下去,刪減邊界判斷的邏輯,大體如下

// handle 函數里
proto.handle = function handle(req, res, out) {
  var idx = 0
  var stack = self.stack;
  
  next();

  function next(err) {
    var layerError = err === 'route'
      ? null
      : err;

    // no more matching layers
    // 函數出口。最后一個調用next,就從這里執行done出去
    if (idx >= stack.length) {
      setImmediate(done, layerError);
      return;
    }

  
    // find next matching layer
    var layer = stack[idx++];
    var match;
    var route = layer.route;
  
    var fn = layer.handle;
    fn(req, res, next);
    
  }
}

總結一下,app.use 就是往中間件數組中塞入新的中間件。中間件的執行則依靠私有方法 app.handle進行處理,按順序尋找中間件,不斷的調用 next 往下執行下一個中間件。

koa

koa 是由 compose 函數執行中間件,其實現抽離成了一個單獨的包,代碼簡單。

function compose (middleware) {
  // ...
  
  return function (context, next) {
    // last called middleware #
    let index = -1
    return dispatch(0)
    
    // 遞歸執行所有的 middleware,返回 Promise 對象。
    function dispatch (i) {
      if (i <= index) return Promise.reject(new Error('next() called multiple times'))
      index = i
      let fn = middleware[i]
      // 如果中間件 next 前的代碼已經執行完了
      // 接下來 fn 就被賦值為 next 后面的代碼。
      if (i === middleware.length) fn = next
      
      if (!fn) return Promise.resolve()
      try {
        // 執行下一個中間件 next 前的代碼。
        return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
}

但比較上文 express 最大的不同並非 async/await 實現了更強大的功能。那就是 next 后還可以執行代碼

const Koa = require('koa');
const app = new Koa();

app.use(async (ctx, next) => {
  console.log('test1')
  next()
  console.log('test3')
});

app.use(async (ctx, next) => {
  console.log('test2')
  next()
  console.log('test4')
});

// response
app.use(async ctx => {
  ctx.body = 'Hello World';
});

// 訪問localhost:3000
// 打印 test1 -> test2 -> test4 -> test3
app.listen(3000);

這就是所謂的 Koa 洋蔥圈模型。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM