这是我参与8月更文挑战的第11天,活动详情查看: 8月更文挑战”juejin.cn/post/698796…
基础介绍
概述
Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。
特点
- 轻量,无绑定
- 中间件架构
- 优雅的API设计
- 增强的错误处理
安装
npm i koa -S
使用
const Koa = require('koa')
// 创建app实例
const app = new Koa();
// 中间件
app.use(async (ctx,next)=>{
ctx.body = "hello world"
})
// 监听端口
app.listen(3000)
原理分析
书读千遍,不如手抄一遍。分析框架的最好办法,自然莫过于动手实现一遍。
主要入口
根据Koa框架的基础使用代码,可以分析出Koa类中主要包含use() 方法和 listen() 方法
// index.js
const http = require("http");
class Koa{
listen(...args){
const server = http.createServer((req,res)=>{
this.callback(req,res)
})
server.listen(...args)
}
use(callback){
this.callback = callback;
}
}
Context对象
Koa为了简化API,引入了上下文Context概念。将原始的请求对象 req 和响应对象 res 封装并挂载到了 context 上,并且设置了 getter 和 setter ,从而简化操作
// request.js
module.exports = {
get url(){
return this.req.url
}
get method(){
return this.req.method.toLowerCase()
}
}
// response.js
module.exports = {
get body(){
return this._body
}
set body(val){
this._body = val
}
}
// context.js
module.exports = {
get url(){
return this.request.url
}
get body(){
return this.response.body
}
set body(val){
this.response.body = val;
}
get method(){
return this.request.method
}
}
// index.js
const http = require("http");
const context = require('./context')
const request= require('./request')
const response= require('./response')
class Koa{
listen(...args){
const server = http.createServer((req,res)=>{
// 创建上下文对象
let ctx = this.createContext(req,res)
this.callback(ctx)
res.end(ctx.body)
})
server.listen(...args)
}
createContext(req,res){
const ctx = Object.create(context)
ctx.request = Object.create(request)
ctx.response = Object.create(response)
ctx.req = ctx.request.req = req
ctx.res = ctx.response.res = res
return ctx
}
use(callback){
this.callback = callback;
}
}
在这里可能有些同学会看的比较懵,ctx.request、ctx.req、ctx.response、ctx.res 看起来都很相似,但具体有什么区别呢?
- ctx.request Koa的Request对象
- ctx.response Koa的Response对象
- ctx.req Node的request 对象
- ctx.res Node的response对象
中间件
Koa 中间件机制就是函数组合的概念,将一组需要顺序执行的函数复合为一个函数,外层函数的参数实际是内层函数的返回值。洋葱圈模型可以形象表示这种机制,是 Koa
源码中的精髓和难点。
同步函数组合
const compose = (...[first,...other]) => (...args)=>{
let ret = first(...args)
other.forEach(fn=>{
ret = fn(ret)
})
return ret
}
const add = (x, y) => x + y
const square = z => z * z
const fn = compose(add,square)
fn(1,2) // 9 = (1+2) * 3
异步函数组合
在中间件中,大部分的应用场景都是异步的。所以需要将同步函数组合改造为异步函数组合的形式
function compose(middlewares){
return function(){
return dispatch(0)
function dispatch(i){
let fn = middlewares[i]
if(!fn){
return Promise.resolve();
}
return Promise.resolve(
fn(function next(){
return dispatch(i+1)
})
)
}
}
}
async function fn1(next) {
console.log("fn1");
await next();
console.log("end fn1");
}
async function fn2(next) {
console.log("fn2");
await delay();
await next();
console.log("end fn2");
}
function fn3(next) {
console.log("fn3");
}
function delay() {
return new Promise((reslove, reject) => {
setTimeout(() => {
reslove();
}, 2000);
});
}
const middlewares = [fn1, fn2, fn3];
const finalFn = compose(middlewares);
finalFn();
fn1
fn2
fn3
end fn2
end fn1
入口改造
// index.js
const http = require("http");
const context = require('./context')
const request= require('./request')
const response= require('./response')
class Koa{
constructor(){
this.middlewares = [];
}
listen(...args){
const server = http.createServer((req,res)=>{
// 创建上下文对象
let ctx = this.createContext(req,res)
// 合成中间件
const fn = this.compose(this.middlewares)
await fn(ctx)
res.end(ctx.body)
})
server.listen(...args)
}
createContext(req,res){
const ctx = Object.create(context)
ctx.request = Object.create(request)
ctx.response = Object.create(response)
ctx.req = ctx.request.req = req
ctx.res = ctx.response.res = res
return ctx
}
use(middleware){
this.middlewares.push(middleware)
}
function compose(middlewares){
return function(ctx){
return dispatch(0)
function dispatch(i){
let fn = middlewares[i]
if(!fn){
return Promise.resolve();
}
return Promise.resolve(
fn(ctx,function next(){
return dispatch(i+1)
})
)
}
}
}
}
测试成果
// index.js
const delay = () => new Promise(resolve => setTimeout(() => resolve() ,2000));
app.use(async (ctx, next) => {
ctx.body = "1";
await next();
ctx.body += "5";
});
app.use(async (ctx, next) => {
ctx.body += "2";
await delay();
await next();
ctx.body += "4";
});
app.use(async (ctx, next) => {
ctx.body += "3";
});
正确答案为:12345