中间件特性
| |
| middleware 1 |
| |
| +-----------------------------------------------------------+ |
| | | |
| | middleware 2 | |
| | | |
| | +---------------------------------+ | |
| | | | | |
| action | action | middleware 3 | action | action |
| 001 | 002 | | 005 | 006 |
| | | action action | | |
| | | 003 004 | | |
| | | | | |
+---------------------------------------------------------------------------------------------------->
| | | | | |
| | | | | |
| | +---------------------------------+ | |
| +-----------------------------------------------------------+ |
+----------------------------------------------------------------------------------+
先写一段贯穿全文的koa的代码
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
await next()
使每个middleware分成,前置操作,等待其他中间件操作可以观察到中间件的特性有:
- 上下文ctx
- await next()控制前后置操作
- 后置操作类似于数据解构-栈,先进后出
promise 的模拟实现
Promise.resolve(middleware1(context, async() => {
return Promise.resolve(middleware2(context, async() => {
return Promise.resolve(middleware3(context, async() => {
return Promise.resolve();
}));
}));
}))
.then(() => {
console.log('end');
});
从这段模拟代码我们可以知道next()返回的是promise,需要使用await去等待promise的resolve值。promise的嵌套就像是洋葱模型的形状就是一层包裹着一层,直到await到最里面一层的promise的resolve值返回。
思考:
- 如果next()不加await执行顺序是什么呢?
在这个例子里面如果只是next()
执行顺序跟await next()
是一样的,因为next的前置操作是同步的 -
如果前置操作是异步的操作呢?
const p = function(args) { return new Promise(resolve => { setTimeout(() => { console.log(args); resolve(); }, 100); }); }; const middleware1 = async (ctx, next) => { await p(1); // await next(); next(); console.log(6); }; const middleware2 = async (ctx, next) => { await p(2); // await next(); next(); console.log(5); }; const middleware3 = async (ctx, next) => { await p(3); // await next(); next(); console.log(4); }; // 输出结果:1,6,2,5,3,4
当程序执行到middleware1,执行到
await p(1)
等待promise值返回跳出然后到下一个事件循环时,执行next()也就是执行到middleware2,再执行到await p(2)
等待promise值返回跳出middleware2,回到middleware1继续执行console.log(6),以此类推输出顺序为1.6.2.5.3.4
Promise的嵌套虽然可以实现中间件流程,但是嵌套的代码会产生可维护性和可读性的问题,也带来中间件扩展的问题。
Koa.js中间件引擎是有koa-compose模块来实现的,也就是Koa.js实现洋葱模型的核心引擎。
koa-compose 实现
this.middleware = [];
use(fn) {
this.middleware.push(fn);
……
}
callback() {
const fn = compose(this.middleware);
……
}
function compose (middleware) {
return function (context, 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()
try {
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
} catch (err) {
return Promise.reject(err)
}
}
}
}
Koa实现的代码非常简洁,我们在使用use的时候将middleware存在一个数组里面,当拦截到请求时执行callback方法,callback中调用了compose,compose方法使用递归执行中间件,遍历完成返回promise.resolve()
,实际最后执行的代码也是上面所讲的promise嵌套的形式。
扩展:Await与Generator
通常我们的都会说await阻塞后面的操作等待promise的resolve返回值或者其他值,如果没有await这个语法糖,要怎么去实现呢?这个等待的过程是怎么控制的呢?
Generator
Generator实际上是一个特殊的迭代器
let gen = null;
function* genDemo(){
console.log(1)
yield setTimeout(()=>{
console.log(3);
gen.next();// c
},100)
console.log(4)
}
gen = genDemo();// a
gen.next(); // b
a. 调用generator,该函数不执行,也就是还没有输出1,返回的是指向内部状态的遍历对象。
b. generator函数开始执行,输出1,遇到第一个yeild表达式停下来,调用gen.next()返回一个对象{value: 10, done:false},这里的value表示setTimeout的一个标识值,也就是调用clearTimeout的参数,是一个数字。done表示遍历还没有结束。100毫秒后输出3;
c. Generator函数从上次在yeild停止的地方一直执行到函数结束(没有其他的yeild),输出4,返回{value: undefined,done:true},表示遍历结束。
可以看到yeild有控制代码进度的作用,是不是跟await有异曲同工之妙
来看下await编译成generator形式的代码,虽然多了一些代码,但是我们可以把_asyncToGenerator(function*() {……}
调用generator,把asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
看成是gen.next();就很容易理解了。
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
try {
var info = gen[key](arg);
var value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
Promise.resolve(value).then(_next, _throw);
}
}
function _asyncToGenerator(fn) {
return function() {
var self = this,
args = arguments;
return new Promise(function(resolve, reject) {
var gen = fn.apply(self, args);
function _next(value) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
}
function _throw(err) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
}
_next(undefined);
});
};
}
const middleware1 =
/*#__PURE__*/
(function() {
var _ref = _asyncToGenerator(function*(ctx, next) {
console.log(1);
yield next();
console.log(6);
});
return function middleware1(_x, _x2) {
return _ref.apply(this, arguments);
};
})();
const middleware2 =
/*#__PURE__*/
(function() {
var _ref2 = _asyncToGenerator(function*(ctx, next) {
console.log(2);
yield next();
console.log(5);
});
return function middleware2(_x3, _x4) {
return _ref2.apply(this, arguments);
};
})();
const middleware3 =
/*#__PURE__*/
(function() {
var _ref3 = _asyncToGenerator(function*(ctx, next) {
console.log(3);
yield next();
console.log(4);
});
return function middleware3(_x5, _x6) {
return _ref3.apply(this, arguments);
};
})();
Promise.resolve(
middleware1(
context,
/*#__PURE__*/
_asyncToGenerator(function*() {
return Promise.resolve(
middleware2(
context,
/*#__PURE__*/
_asyncToGenerator(function*() {
return Promise.resolve(
middleware3(
context,
/*#__PURE__*/
_asyncToGenerator(function*() {
return Promise.resolve();
})
)
);
})
)
);
})
)
).then(() => {
console.log("end");
});
参考链接:
https://chenshenhai.github.io...
https://segmentfault.com/a/11...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。