Koa中的中間件不一樣於Express,Koa使用了洋蔥模型。神奇的Koa框架僅僅只包含了4個文件。今天咱們只看一下主文件—application.js,它包含了中間件如何工做的核心邏輯。node
git clone git@github.com:koajs/koa.git npm install
而後咱們在項目根目錄添加一個index.js文件,做測試用途。git
// index.js // Include the entry file of koa const Koa = require('./lib/application.js'); const app = new Koa(); const debug = require('debug')('koa'); app.use(async (ctx, next) => { console.log(1); await next(); console.log(6); const rt = ctx.response.get('X-Response-Time'); console.log(`${ctx.method} ${ctx.url} - ${rt}`); }); // time logger here app.use(async (ctx, next) => { console.log(2); const start = Date.now(); await next(); console.log(5); const ms = Date.now() - start; ctx.set('X-Response-Time', `${ms}ms`); }); app.use(async (ctx, next) => { console.log(3); ctx.body = 'Hello World'; await next(); console.log(4); }); app.listen(3000);
運行服務器:github
node index.js
訪問http://localhost:3000,你將看到1, 2, 3, 4, 5, 6的輸出,這叫作洋蔥模型(中間件)npm
讓咱們來看看Koa的核心代碼,瞭解一下中間件的工做原理。在index.js文件中,咱們能夠這樣使用中間件:api
const app = new Koa(); app.use(// middleware); app.use(// middleware); app.listen(3000);
而後再來看看application.js,下面的代碼是和中間件有關的,我在代碼中加了一下備註。promise
const compose = require('koa-compose'); module.exports = class Application extends Emitter { constructor() { super(); this.proxy = false; // Step 0: init a middleware list this.middleware = []; } use(fn) { // Step 1: adding the middleware to the list this.middleware.push(fn); return this; } listen(...args) { debug('listen'); // Step 2: using this.callback() to compose all middleware const server = http.createServer(this.callback()); return server.listen(...args); } callback() { // Step 3: This is the most important part - compose, it group all // middleware to one big function and return a promise, we will talk more // about this function const fn = compose(this.middleware); if (!this.listenerCount('error')) this.on('error', this.onerror); const handleRequest = (req, res) => { const ctx = this.createContext(req, res); return this.handleRequest(ctx, fn); }; return handleRequest; } handleRequest(ctx, fnMiddleware) { const res = ctx.res; res.statusCode = 404; const onerror = err => ctx.onerror(err); const handleResponse = () => respond(ctx); onFinished(res, onerror); // Step 4: Resolve the promise return fnMiddleware(ctx).then(handleResponse).catch(onerror); } }
關於compose函數的更多信息,咱們來看看koa-compose包服務器
module.exports = compose function compose (middleware) { // skipped type checking code here return function (context, next) { // last called middleware # let index = -1 return dispatch(0) function dispatch (i) { if (i <= index) return Promise.reject(new Error('next() called multiple times')) index = i let fn = middleware[i] if (i === middleware.length) fn = next if (!fn) return Promise.resolve() try { return Promise.resolve(fn(context, dispatch.bind(null, i + 1))); } catch (err) { return Promise.reject(err) } } } }
全部的中間件都傳遞給compose函數,它將返回dispatch(0),dispatch函數將當即執行並返回一個promise。在咱們理解dispatch函數的內容前,咱們必須先了解promise的語法。app
一般咱們是這樣使用promise的:框架
const promise = new Promise(function(resolve, reject) { if (success){ resolve(value); } else { reject(error); } });
在Koa中,promise是這樣使用的:koa
let testPromise = new Promise((resolve, reject) => { setTimeout(() => { resolve('test success'); }, 1000); }); Promise.resolve(testPromise).then(function (value) { console.log(value); // "test success" });
因此,咱們知道,在compose函數中,它返回一個promise
module.exports = compose function compose (middleware) { // skipped type checking code here return function (context, next) { // last called middleware # let index = -1 return dispatch(0) function dispatch (i) { if (i <= index) return Promise.reject(new Error('next() called multiple times')) index = i let fn = middleware[i] if (i === middleware.length) fn = next if (!fn) return Promise.resolve() try { return Promise.resolve(fn(context, dispatch.bind(null, i + 1))); } catch (err) { return Promise.reject(err) } } } }
dispatch是一個遞歸函數,它將遍歷全部的中間件。在咱們的index.js文件中,咱們有三個中間件,這三個中間件將在await next()前執行代碼
app.use(async (ctx, next) => { console.log(2); const start = Date.now(); await next(); // <- stop here and wait for the next middleware complete console.log(5); const ms = Date.now() - start; ctx.set('X-Response-Time', `${ms}ms`); });
咱們能夠看看三個中間件的執行順序:
dispatch(0), Promise.resolve(fn(context, dispatch.bind(null,0+1)))
被執行await next()
next() = dispatch.bind(null, 0+1)
await next()
next() = dispatch.bind(null, 1+1)
await next()
next() = dispatch.bind(null, 2+1)
, 沒有第四個中間件,當即返回 if(!fn) return Promise.resolve()
, 在第三個中間件中的await next()
被 resolved, 並執行第三個中間件剩下的代碼await next()
被resolve,並執行第二個中間件剩下的代碼await next()
被resolve,並執行第一個中間件剩下的代碼若是在中間件中有async/await,編碼會變得更加的簡單。當咱們想寫一個針對api請求的時間記錄器,將會是一件很是簡單的事:
app.use(async (ctx, next) => { const start = Date.now(); await next(); // your API logic const ms = Date.now() - start; console.log('API response time:' + ms); });