官网代码示例css
1.新建一个项目,命名为koa2-test
node
2.在命令行中,进入koa2-test
,执行npm init -y
npm
npm init -y
复制代码
3.将此项目中的package.json
中的"main":"index.js"
替换为"main":"app.js"
json
npm install koa --save
复制代码
4.建立app.js
文件,将以下代码复制到app.js
中,(一共有三个中间件),如下代码为官网示例代码api
const Koa = require('koa');
const app = new Koa();
// logger 记录日志
app.use(async (ctx, next) => {
await next();
const rt = ctx.response.get('X-Response-Time');
console.log(`${ctx.method} ${ctx.url} - ${rt}`);
});
// x-response-time 处理请求时间
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
ctx.set('X-Response-Time', `${ms}ms`);
});
// response
app.use(async ctx => {
ctx.body = 'Hello World';
});
app.listen(3000);
复制代码
5.在命令行中启动(启动命令以下)数组
node app.js
复制代码
6.浏览器访问 localhost:3000promise
命令行中打印以下:浏览器
代码执行流程解析:bash
1.先注册3个中间件,再监听3000
端口。app
2.在第一个logger中间件执行到await next();
时,下面的代码先不执行,继续执行下一个中间件x-response-time
,直到遇到ctx.body
,再开始逆向执行await next();
下的内容,最终打印出响应所需的时间。 此流程即为洋葱圈模型:(此图为网上搜索获得)
将代码进行以下修改,加入注释
const Koa = require('koa');
const app = new Koa();
// logger 记录日志
app.use(async (ctx, next) => {
console.log("第一层洋葱---开始")
await next();
const rt = ctx.response.get('X-Response-Time');
console.log(`${ctx.method} ${ctx.url} - ${rt}`);
console.log("第一层洋葱---结束")
});
// x-response-time 处理请求时间
app.use(async (ctx, next) => {
console.log("第二层洋葱---开始")
const start = Date.now();
await next();
const ms = Date.now() - start;
ctx.set('X-Response-Time', `${ms}ms`);
console.log("第二层洋葱---结束")
});
// response
app.use(async ctx => {
console.log("第三层洋葱---开始")
ctx.body = 'Hello World';
console.log("第三层洋葱---结束")
});
app.listen(3000);
复制代码
请求在命令行打印以下:这样就清楚的解释了洋葱圈模型(由外向内执行,再由内向外执行)
此行为用洋葱来形容很是的形象,先由外向内执行一个一个的next,等执行到最中心,再由内向外一层层执行。
从上面的例子中咱们能够进行分析,中间件是如何进行实现的? 猜想至少应该有两个步骤:
1.app.use
用来注册中间件,并进行收集
2.实现next
机制:经过上一个next
触发下一个next
// 引入http
const http = require('http')
// 组合中间件
function compose(middlewareList) {
return function (ctx) {
// 中间件调用的逻辑
function dispatch(i) {
const fn = middlewareList[i]
try {
return Promise.resolve(
// 执行中间件,并封装为Promise,格式兼容
fn(ctx, dispatch.bind(null, i + 1)) // Promise
)
} catch (err) {
return Promise.reject(err)
}
}
return dispatch(0)
}
}
// 定义构造函数
class Koa2 {
constructor () {
// 中间件数组
this.middlewareList = []
}
use(fn) {
this.middlewareList.push(fn)
return this
}
// 将req和res组合为ctx
createContext(req, res) {
const ctx = {
req,
res
}
ctx.query = req.query
return ctx
}
handleRequest(ctx, fn) {
return fn(ctx)
}
callback() {
const fn = compose(this.middlewareList)
return (req, res) => {
const ctx = this.createContext(req, res)
return this.handleRequest(ctx, fn)
}
}
// 建立服务并监听 ...args传入多个参数
listen(...args) {
const server = http.createServer(this.callback())
server.listen(..args)
}
}
module.exports = Koa2
复制代码
结构为:在class Koa2
中有 use createContext callback listen
等方法!经过use
方法来收集中间件。 compose
为组合中间件的方法,从而实现next()
,其中Promise.resolve()
是为了防止,在使用app.use()
时没有使用async
包裹,就返回的不是promise
函数,Promise.resolve()
包裹后就一直返回promise
。
将fn(ctx, dispatch.bind(null, i + 1))
包裹在Promise.resolve()
中,fn
为async
函数
Promise.resolve(value)方法返回一个以给定值解析后的Promise
对象。若是该值(指代value
)为promise
,返回这个promise
;若是value值为promise
,返回这个promise
。此函数将类promise
对象的多层嵌套展平。
在MDN
的解释中,若是Promise.resolve(value)
中的value
值为promise
,则返回这个promise
。在KOA2
中,中间件为async await
包裹的异步函数,而async await
是promise
的语法糖。所以即便用Promise.resolve(value)
把中间件进行了包裹,也会不想影响结果,并且避免了中间件没有使用async await
时的报错。
在这篇文章中也有说起。
compose
为组合中间件的方法,其实也就不难看出,整个中间件的核心功能就在compose
,此方法将中间件push
到middlewareList
中。
所以重点在于在compose
中进行递归,在监听到request
请求的时候,将上下文对象ctx
传入其中,最终使全部中间件按照洋葱圈
模型执行。
当middlewares
数组合成到最后一个中间件的时候,则直接返回,此时递归则结束。
Promise.resolve()
复制代码
递归的返回值为何要通过Promise.resolve()
的包裹呢?由于涉及到async、await
等相关的异步操做。若在使用app.use()
时未用async
包裹则会发生错误。
咱们最终的目的是返回一个可接收上下文参数ctx
的函数,所以须要对dispatch
进行进一步的包装,就造成了咱们最终的compose
,dispatch
执行的过程是一个递归的过程。
function compose(middlewareList) {
reutrn function(ctx) {
function dispatch(i) {
const fn = middlewareList[i]
try {
return Promise.resolve(
fn(ctx, dispatch.bind(null, i+1))
)
} catch (err) {
return Promise.reject(err)
}
}
reutrn dispatch(0)
}
}
复制代码
KOA源码结构以下图:
lib
文件夹下放着四个
KOA2
核心文件,
application.js、context.js、request.js、response.js
koa-compose
源码以下
'use strict'
/**
* Expose compositor.
*/
module.exports = compose
/**
* Compose `middleware` returning
* a fully valid middleware comprised
* of all those which are passed.
*
* @param {Array} middleware
* @return {Function}
* @api public
*/
function compose (middleware) {
if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
for (const fn of middleware) {
if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
}
/**
* @param {Object} context
* @return {Promise}
* @api public
*/
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)
}
}
}
}
复制代码