koa -- 基于 Nodejs 平台的下一代 web 开发框架
前言
Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。 通过利用 async 函数,Koa 帮你丢弃回调函数,并有力地增强错误处理。 Koa 并没有捆绑任何中间件, 而是提供了一套优雅的方法,帮助您快速而愉快地编写服务端应用程序。
尽管提供了相当多的有用的方法 Koa 仍保持了一个很小的体积,因为没有捆绑中间件。这包括诸如内容协商,缓存清理,代理支持和重定向等常见任务的方法。
虽然 koa 官网的学习文档只有简简单单的一页,但是它所有的精髓都体现得淋漓尽致,总是能够让你想要深入其原理的冲动
初出茅庐
我们以现已成为传统的 “hello world” 案例来开始吧
Koa 中间件以更传统的方式级联,使用 async 功能,我们可以实现 “真实” 的中间件。通过一系列功能直接传递控制,直到一个返回,Koa 调用“下游”,然后控制流回“上游”。
const Koa = require('koa') // 引入 koa
const app = new Koa() // 实例化 koa
app.use(async (ctx, next) => { // 所有请求都经过该异步处理函数(中间件)
ctx.body = 'Hello World' // 响应内容
})
app.listen('3000') // 监听端口
深入理解 Koa 中间件之洋葱模型
先来测试一段代码
const Koa = require('koa')
const app = new Koa()
app.use(async (ctx, next) => {
console.log(1)
next()
console.log(2)
})
app.use(async (ctx, next) => {
console.log(3)
next()
console.log(4)
})
你如果说输出的是 1 2 3 4
最终结果应该是 1 3 4 2
是不是一脸懵??????这就是接下来要说的洋葱模型
-
koa 中间件的执行很像一个洋葱,但并不是一层一层的执行,而是以 next 为分界,先执行本层中 next 以前的部分,当下一层中间件执行完后,再执行本层 next 以后的部分。
-
一个洋葱结构,从上往下一层一层进来,再从下往上一层一层回去,是不是越来越有 feel
如果你愿意 一层一层一层的剥开我的心
middleware 的核心原理
middleware 是从 http 请求开始到响应结束的过程中处理的逻辑,所以需要拿到请求和响应对它们进行一定的逻辑处理。还需要考虑的一个问题是多个中间件共存的问题,考虑怎么样让多个中间件自动执行。
那么,middleware 的模型大致也就出来了
const middleware = (req, res, next) => {
next()
}
接下来,我们来写几个简单的案例来看看中间件的实现过程
// 定义几个中间件函数
const m1 = (req, res, next) => {
console.log('m1 run')
next()
}
const m2 = (req, res, next) => {
console.log('m2 run')
next()
}
const m3 = (req, res, next) => {
console.log('m3 run')
next()
}
// 中间件集合
const middlewares = [m1, m2, m3]
function use(req, res) {
const next = () => {
// 获取第一个中间件
const middleware = middlewares.shift()
if (middleware) {
middleware(req, res, next)
}
}
next()
}
// 第一次请求流进入
use()
// 结果
m1 run
m2 run
m3 run
当然考虑到中间件中有异步的场景,那么我们就要把 next 放在该中间件的异步回调中执行,这样才能保证其执行顺序的正确性
const m2 = (req, res, next) => fetch('xxx').then(() => next())
还有一种中间件的场景,比如说日志中间件、请求监控中间件等,他们会在业务处理前和处理后都会执行相关逻辑,这个时候就要求我们需要能对 next 函数进行二次处理,我们可以把它的返回值包装成 Promise,使得其在业务处理完成之后通过 then 回调继续处理中间件逻辑
function use(req, res, next) {
const next = () => {
const middleware = middlewares.shift()
if (middleware) {
return Promise.resolve(middleware(req, res, next))
} else {
return Promise.resolve('end')
}
}
next()
}
这个时候我们就可以通过如下方式调用了
const m1 = (req, res, next) => {
console.log('m1 start')
next().then(() => {
console.log('m1 end')
})
}
以上就是实现 middleware 的一个设计模式,当然,我们可以使用 async/await 实现,写法会更加优雅
const m1 = async (req, res, next) => {
let result1 = await next()
}
const m2 = async (req, res, next) => {
let result2 = await next()
}
const m3 = async (req, res, next) => {
let result3 = await next()
}
const middlewares = [m1, m2, m3]
function use(req, res) {
const next = () => {
const middleware = middlewares.shift()
if (middleware) {
return Promise.resolve(middleware(req, res, next))
} else {
return Promise.resolve('end')
}
}
next()
}
use()
在 koa2 框架中,中间件的实现方式也是将 next 方法返回值封装成 Promise 对象,实现了洋葱模型的调用流程
koa middleware 的实现方式
function compose(middleware) {
// 判断中间件类型
if (!Array.iaArray(middleware)) throw new TypeError('Middleware stack must be an array!')
for (const fn of middleware) {
// 中间件必须为函数类型
if (typeof fn !== 'function') throw new TypeError('Middleware stack must be composed of functions!')
}
return function(context, next) {
// 采用闭包将索引缓存,来实现调用计数
let index = -1
return dispatch(0)
function dispatch(i) {
// 防止 next 方法重复调用
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)
}
}
}
}
小结
所以,今天你又成就感满满了吗??????
最后 在此感谢徐小夕提供的思路,并且期待这周六我们的聚餐(算算也有半年多没有聚了)