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); });