Qimingweikun 2019-10-31

看见了吧,刚入坑koa总是会看见一张洋葱图(后面再说吧)
koa.js是一个框架。
① 对于 HTTP 服务
当前端 UI 向服务器发起了一个 HTTP 请求时,koa.js 能够在 HTTP 请求发送后 (要搞清楚,是已经发送出去的请求,并不是像 axios 一样拦截 request )对于该请求的 request 进行处理
既然能对请求的 request 进行处理,那么 koa.js 也能对于服务端返回的 HTTP 响应(跟上述一样,是已经发出的 response )进行处理。
② 对于中间件容器
对于这个中间件容器,我也没什么概念,于是乎... 我去找了下相关的内容。
什么是中间件容器呢?
在一个大型分布式的系统中, 负责各个不同组件和服务之间的管理和交互。(哎,简单来说点就是你,你爸,你妈,你想要找你爸要点零花钱,但是必须通过你妈哈哈哈哈哈)
比如呢,在一个分布式系统中,有N个数据库,数据库按业务模块分配,但是,随时间增加,业务会改变,而对应的数据库的分配也需要改变。这时候,你如果想要修改某些数据,直接操作数据库是不太可能了,那这时候就需要一个中间件,由它来负责对最终的数据库读写,而访问者只是带着一些参数啥的来访问这个中间件。
言归正传... 切回到主题 koa.js 对于中间件容器能干嘛呢?
第一点:中间的加载
第二点:中间的执行
对于 koa.js的作用也就这两点了。
在使用 Koa.js 的过程中,会发现中间件的使用都是如下所示:
const Koa = require(‘koa‘);
let app = new Koa();
const middleware1 = async (ctx, next) => {
console.log(1);
await next();
console.log(6);
}
const middleware2 = async (ctx, next) => {
console.log(2);
await next();
console.log(5);
}
const middleware3 = async (ctx, next) => {
console.log(3);
await next();
console.log(4);
}
app.use(middleware1);
app.use(middleware2);
app.use(middleware3);
app.use(async(ctx, next) => {
ctx.body = ‘hello world‘
})
app.listen(3001)
// 启动访问浏览器
// 控制台会出现以下结果
// 1
// 2
// 3
// 4
// 5
// 6问题来了,为什么会出现以上的结果?
这个主要是 Koa.js 的一个中间件引擎 koa-compose 模块来实现的,也就是 Koa.js 实现洋葱模型的核心~~
从洋葱模型可以看出来(怎么看?就是从左到右穿过去.. 先碰到最外圈.. 最后也是碰到的最外圈),中间件在 await next() 前后的操作,很像数据结构-----“栈”,先进后出,符合洋葱模型。同时,在上面的代码中还有 ctx,这个是就是上下文管理操作数据了(哎,写详细点吧,这个上下文就是来保持两个函数之间传递的时候的一些参数状态吧)。于是乎我们可以总结出如果要实现洋葱模型需要具有的几点特性。
① 需要有统一的 上下文 ctx
② 操作先进后出
③ 有控制先进后出的机制 next
④ 有提前结束的机制
知道了这几个特性,可以自己单纯的用 Promise 做个实现。
// 定义一个上下文
let context = {
data: []
};
// 定义一个中间件 middleware1
async function middleware1( ctx, next ){
console.log(‘action 001‘);
ctx.data.push(1);
await next();
console.log(‘action 006‘);
ctx.data.push(6);
}
// 定义一个中间件 middleware2
async function middleware2(ctx, next) {
console.log(‘action 002‘);
ctx.data.push(2);
await next();
console.log(‘action 005‘);
ctx.data.push(5);
}
// 定义一个中间件 middleware3
async function middleware3(ctx, next) {
console.log(‘action 003‘);
ctx.data.push(3);
await next();
console.log(‘action 004‘);
ctx.data.push(4);
}
Promise.resolve(middleware1(context, async() => {
return Promise.resolve(middleware2(context, async() => {
return Promise.resolve(middleware3(context, async() => {
return Promise.resolve();
}));
}));
})).then(() => {
console.log(‘end‘);
console.log(‘context = ‘, context);
});
// 结果显示
// "action 001"
// "action 002"
// "action 003"
// "action 004"
// "action 005"
// "action 006"
// "end"
// "context = { data: [1, 2, 3, 4, 5, 6]}"然而,虽然单纯用 Promise 嵌套 可以直接实现中间件流程,但是这样会产生代码可读性和可维护性的问题,也带来了中间件扩展的问题。所以,这需要把 Promise 嵌套 实现的中间件方式进行高度抽象,达到可以自定义中间件的层数。这时候就需要借助神器 async/await 。
首先理清一下需要的步骤:
根据上面中间件分析的原理,可以抽象出
const compose = require(‘./index‘);
// 中间件数组
let middleware = [];
// 上下文
let context = {
data: []
};
// 添加第一个中间件
middleware.push(async(ctx, next) => {
console.log(‘action 001‘);
ctx.data.push(2);
await next();
console.log(‘action 006‘);
ctx.data.push(5);
});
// 添加第二个中间件
middleware.push(async(ctx, next) => {
console.log(‘action 002‘);
ctx.data.push(2);
await next();
console.log(‘action 005‘);
ctx.data.push(5);
});
// 添加第三个中间件
middleware.push(async(ctx, next) => {
console.log(‘action 003‘);
ctx.data.push(2);
await next();
console.log(‘action 004‘);
ctx.data.push(5);
});
// 得到一个函数,相当于是一个中间件的链吧
const fn = compose(middleware);
// 执行fn函数,传入上下文
fn(context)
.then(() => {
console.log(‘end‘);
console.log(‘context = ‘, context);
});以下是compose
module.exports = compose;
// 放入中间件
function compose(middleware) {
//判断是不是数组
if (!Array.isArray(middleware)) {
throw new TypeError(‘Middleware stack must be an array!‘);
}
// 返回一个方法
return function(ctx, next) {
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();
}
// 这个就是拼装一个 Promise 的链子吧
try {
return Promise.resolve(fn(ctx, () => {
return dispatch(i + 1);
}));
} catch (err) {
return Promise.reject(err);
}
}
};
}至此原理已经看透透,其实就是一个 诺言 的链子~ 来进行的中间件调用~
还未讲完~~~ 后文待续~~~~
ε=(´ο`*)))唉
借鉴原文 https://chenshenhai.github.io/koajs-design-note/note/chapter01/05.html