用法
koa 框架里面的中间件用法
同步示例
app.use((ctx, next) => {
console.log(1)
next()
console.log(3)
})
app.use((ctx) => {
console.log(2)
})
// 1 => 2 => 3
异步示例
const Koa = require('koa');
const app = new Koa();
app.use(async (ctx, next) => {
console.log(1);
await next();
console.log(1.1);
});
app.use(async (ctx, next) => {
console.log(2);
await next();
console.log(2.2);
});
app.use(async (ctx, next) => {
console.log(3);
await next();
console.log(3.3);
});
打印结果
// 1
// 2
// 3
// 3.3
// 2.2
// 1.1
当程序运行到await next()的时候就会暂停当前程序,进入下一个中间件,处理完之后才会仔回过头来继续处理。也就是说,当一个请求进入,#1会被第一个和最后一个经过,#2则是被第二和倒数第二个经过,依次类推。
注意:每个 use 里面都是一个中间件,这中间件可以自己编写,也有很多第三方的,app.use 经常可以见到
洋葱模型
实际用法举例
const Koa = require("koa");
const app = new Koa();
// 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`);
});
// logger
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
console.log(`${ctx.method} ${ctx.url} - ${ms}`);
});
// response
app.use(async ctx => {
ctx.body = "Hello World";
});
app.listen(3000);
基本上,Koa 所有的功能都是通过中间件实现的。
简单源码实现
思路:use函数中push中间件函数进数组;;;遇到next则执行下一个中间件
下面这个是实际的源码,可以扫一眼之后,往下看
class Koa {
constructor () {
this.middlewares = []
}
use (callback) {
this.middlewares.push(callback)
}
}
var app = new Koa()
app.use(async (ctx, next) => {
console.log(1)
await next()
console.log(2)
})
app.use(async (ctx, next) => {
console.log(3)
await next()
console.log(4)
})
app.use(async (ctx, next) => {
console.log(5)
})
function compose (middlewares) {
let index = -1
return dispatch(0)
function dispatch (i) {
index = i
if (i === middlewares.length) return
let fn = middlewares[i]
let result = fn(null, function next() { // 这里的null应该是context,
return dispatch(i+1) // 世界上核心的思想就是,next()时先执行下一个middleware
})
return Promise.resolve(result)
}
}
compose(app.middlewares)
如果上面已经看懂了,就不需要往下看了。。。
开始源码
首先来实现一个简单的同步的例子
class Koa {
constructor () {
this.middlewares = []
}
use (fn) {
this.middlewares.push(fn)
}
}
var app = new Koa()
// 中间件1
app.use(function (next) {
console.log(1)
next()
console.log(2)
})
// 中间件2
app.use(function (next) {
console.log(3)
})
function compose (middlewares) {
function dispatch (index) {
if (index === middlewares.length) return // 所有中间件都执行完了
const fn = middlewares[index] // 获得中间件
fn( // 注意这是一个中间件函数执行,下面的 function next 是参数,函数作为参数,所以中间件中才能执行这个函数
// 开始执行中间件,打印console.log(1)
function next(){ // 相当于 自建了一个 next 函数,等待中间件中 await next() 调用,这行中的next就是个标识,可以不写
// 中间件中执行的await next() 就相当于执行了这个函数
// 直接进入下一个中间件执行
dispatch(index+1)
}
)
}
dispatch(0)
}
compose(app.middlewares) // 1 3 2
// 整个过程就相当于
function next () {
m2()
}
function m2 () {
console.log(3)
}
function m1 () {
console.log(1)
next()
console.log(2)
}
m1() // 执行
同步比较好理解吧,接下来就看异步,中间件为 async 函数,await next()
首先先看一下一个知识点:
async function test() {
var res = await ajax()
console.log('res', res)
}
function ajax() {
new Promise(resolve => { // 这里没 return
setTimeout(() => {
resolve(1)
console.log(2)
}, 2000)
})
}
test()
// 输出
// res: undefined
// 2秒后输出2
你会发现,res 没有 await 等待2秒后的 resolve。
是因为ajax在第7行并没有 return ,也就是说 ajax()返回的是 undefined ,而不是promise,等同于下面这样
async function test() {
var res = await ajax()
console.log('res', res)
}
function ajax() {
setTimeout(() => {
console.log(2)
}, 2000)
}
test()
说白了,你如果 await 后面的 ajax() 不返回一个 promise 对象,那么 await 这关键字就认为没啥用了
var res = await ajax()
===
var res = ajax()
因此,若想发挥 await 的等待的作用,一定要后面返回的是 promise 对象
开始异步代码分析
前言,在以前 node 不支持 async 和 await 的时候,compose 的异步 promise 支持,代码原理非常麻烦,各种 .then
但是,当前 node 已经支持 async 和 await,看下面
改成异步:
async function next () {
return m2()
}
async function m2 () { // 异步中间件2
return new Promise(resolve => {
setTimeout(() => {
resolve()
console.log(3)
}, 2000)
})
}
async function m1 () { // 异步中间件1
console.log(1)
await next() // 等待
console.log(2)
}
m1() // 执行
// 1
// 3
// 2
可以看到,2的输出确实是等待了await 2秒后了。说明了啥?
说明了 await 后面的 next() ,你只要返回的是正常的promise对象,那么即使你是异步,和同步的效果是一样的:
什么效果?就是 m1 里面每一行都按顺序来啊,console.log(2) 还是会等待 next()啊,再回看下同步
// 整个过程就相当于
function next () {
m2()
}
function m2 () {
console.log(3)
}
function m1 () {
console.log(1)
next() // 同步代码,本身就先执行完 next() 再下一步
console.log(2)
}
m1() // 执行
所以对于 m1 来说,执行到 next 那行代码时
同步情况:next(),本身就要等next()执行完再执行下面的代码
异步情况:await next() ,有await起作用,等待,n 秒后 resolve ,再执行后续的代码
总结:
说了这么多,我们的目的是什么?compose 最核心的本质是什么?
洋葱模型的最核心的本质是:
function m1 () {
console.log(1)
next() // 去执行下一个中间件 m2,一定要完全执行完后,才能继续下面的代码!!!
console.log(2)
}
本质:console.log(2)一定要在next()完全完事后再执行
即:next()执行下一个中间件的这个过程,一定要中断 m1 ,等next()完全执行完后,再继续 m1
app.use(async next=>{
console.log(1)
await next()
console.log(1.1)
})
app.use(async next=>{
console.log(2)
await next()
console.log(2.1)
})
app.use(async next=>{
console.log(3)
await next()
console.log(3.1)
})
app.use(async next=>{
console.log(4)
})
输出顺序:1 2 3 4 3.1 2.1 1.1
console.log(1) 一定要等 await next()完全执行完后,才执行 console.log(1.1)
而 2 3 4 3.1 2.1 是不是就是next完全执行完?
function m1 () {
console.log(1)
await next() // 必须保证完全执行完
console.log(2)
}
因此对于异步来说,核心就是保证 await next()完全执行完毕
保证1:必须写 await 关键字,否则无效
保证2:next() 必须返回的是 promise 对象,否则无效
因此,对于异步源码来说,仅需对同步源码进行改造,完成保证2中返回 promise 对象即可
因为保证1中的 await ,是我们用的时候手写的啊,又不是源码中的。。。
注意别忘了:
await等待到啥时候,是取决于后面promise中何时 resolve
关于 async 和 await 忘记了的,还得去看 async 原理那个文章
异步源码:
class Koa {
constructor () {
this.middlewares = []
}
use (fn) {
this.middlewares.push(fn)
}
}
var app = new Koa()
// 中间件1
app.use(async function (next) {
console.log(1)
await next()
console.log(2)
})
function ajax () {
return new Promise(resolve => {
setTimeout(() => {
resolve()
console.log(5)
}, 2000)
})
}
// 中间件2
app.use(async function (next) {
await ajax()
console.log(3)
})
function compose (middlewares) {
function dispatch (index) {
if (index === middlewares.length) return // 所有中间件都执行完了
const fn = middlewares[index] // 获得中间件
let res = fn( // 注意这是一个中间件函数执行,下面的 function next 是参数,函数作为参数,所以中间件中才能执行这个函数
// 开始执行中间件,打印console.log(1)
function next(){ // 相当于 自建了一个 next 函数,等待中间件中 await next() 调用,这行中的next就是个标识,可以不写
// 中间件中执行的await next() 就相当于执行了这个函数
// 直接进入下一个中间件执行
return dispatch(index+1)
// 这里的 return 为了确保 next()有返回值,而若想返回值为promise对象,那是不是得 dispatch()返回?
}
)
// 注意,这里才是 dispatch()返回值得地方,对应第39行。必须要返回 promise ,供 await 使用
// 而返回什么 promise 对象啊?返回的当然是你要 await 啥了啊
// await next(), next() 的本质是 完全执行完下一个中间件函数
// ,因此 await 等待的 promise 对象,就是中间件函数执行完后返回的promise !
// 来看看中间件函数是啥
/* async function (next) {
console.log(3)
}
*/
// 这就是要执行的下一个中间件函数啊,也就是第34行的fn啊
// 那目的就成为 fn() 执行后要返回一个 promise
// 而 fn 本身就是带有 async 的,async 关键字的函数本身就返回 promise !
// 因此如果你中间件函数直接写 async 了,那直接返回 res 就行了!
return res // async本身返回的就是 promise
// 但是源码中会有判断你中间件函数写没写 async,因此如果fn()返回的是乱七八糟的,兼容都转成promise了
// return Promise.resolve(res) // 不管你 res是啥,全都转成 promise
}
dispatch(0)
}
compose(app.middlewares)
从34行看到61行,仔细想下,注意中间件2中还有个await ajax(),目的是模拟 next()是一个n秒的异步,await 等待他