image.png概述:Koa 是一个新的 web 框架, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。

    1. koa是Express的下一代基于Node.js的web框架
    2. koa2完全使用Promise并配合 async 来实现异步

    举例:

    1. const Koa = require('koa')
    2. const app = new Koa()
    3. app.use((ctx, next) =>
    4. {
    5. ctx.body = [ { name: 'tom' } ]
    6. next()
    7. })
    8. //日志
    9. app.use(async (ctx,next) =>
    10. {
    11. const start = Date.now()
    12. await next()
    13. const end = Date.now()
    14. console.log(`请求${ctx.url} 耗时${parseInt(end - start)}ms`)
    15. })
    16. app.listen(3000)

    koa 原理:
    一个基于nodejs的入门级http服务,类似下面代码:

    1. const http = require('http')
    2. const server = http.createServer((req, res)=>{
    3. res.writeHead(200)
    4. res.end('hi kaikeba')
    5. })
    6. server.listen(3000,()=>{
    7. console.log('监听端口3000')
    8. })

    koa的目标是用更简单化、流程化、模块化的方式实现回调部分

    1. const http = require("http");
    2. class KKB {
    3. listen(...args) {
    4. const server = http.createServer((req, res) => {
    5. this.callback(req, res);
    6. });
    7. server.listen(...args);
    8. }
    9. use(callback) {
    10. this.callback = callback;
    11. }
    12. }
    13. module.exports = KKB;
    1. // 调用,index.js
    2. const KKB = require("./kkb");
    3. const app = new KKB();
    4. app.use((req, res) => {
    5. res.writeHead(200);
    6. res.end("hi kaikeba");
    7. });
    8. app.listen(3000, () => {
    9. console.log("监听端口3000");
    10. });

    koa为了能够简化API,引入上下文context概念,将原始请求对象req和响应对象res封装并挂载到 context上,并且在context上设置getter和setter,从而简化操作

    1. // request.js
    2. module.exports = {
    3. get url() {
    4. return this.req.url;
    5. },
    6. get method(){
    7. return this.req.method.toLowerCase()
    8. }
    9. };
    10. // response.js
    11. module.exports = {
    12. get body() {
    13. return this._body;
    14. },
    15. set body(val) {
    16. this._body = val;
    17. }
    18. };
    19. // context.js
    20. module.exports = {
    21. get url() {
    22. return this.request.url;
    23. },
    24. get body() {
    25. return this.response.body;
    26. },
    27. set body(val) {
    28. this.response.body = val;
    29. },
    30. get method() {
    31. return this.request.method
    32. }
    33. };

    image.png

    1. // kkb.js
    2. // 导入这三个类
    3. const context = require("./context");
    4. const request = require("./request");
    5. const response = require("./response");
    6. class KKB {
    7. listen(...args) {
    8. const server = http.createServer((req, res) => {
    9. // 创建上下文
    10. let ctx = this.createContext(req, res);
    11. this.callback(ctx)
    12. // 响应
    13. res.end(ctx.body);
    14. });
    15. // ...
    16. }
    17. // 构建上下文, 把res和req都挂载到ctx之上,并且在ctx.req 和ctx.request.req同时保存
    18. createContext(req, res) {
    19. const ctx = Object.create(context);
    20. ctx.request = Object.create(request);
    21. ctx.response = Object.create(response);
    22. ctx.req = ctx.request.req = req;
    23. ctx.res = ctx.response.res = res;
    24. return ctx;
    25. }
    26. }

    中间件
    Koa中间件机制:Koa中间件机制就是函数式 组合概念 Compose的概念,将一组需要顺序执行的函数复合为一个函数,外层函数的参数实际是内层函数的返回值。洋葱圈模型可以形象表示这种机制,是源码中的精髓和难点。
    image.png

    多个函数组合

    1. const compose = (fn1, fn2) => (...args) => fn2(fn1(...args))
    2. const fn = compose(add,square)
    3. const compose = (...[first,...other]) => (...args) => {
    4. let ret = first(...args)
    5. other.forEach(fn => {
    6. ret = fn(ret)
    7. })
    8. return ret
    9. }
    10. const fn = compose(add,square)
    11. console.log(fn(1, 2))

    异步中间件:上面的函数都是同步的,挨个遍历执行即可,如果是异步的函数呢,是一个 promise,我们要支持async + await的中间件,所以我们要等异步结束后,再执行下一个中间件。

    1. function compose(middlewares) {
    2. return function() {
    3. return dispatch(0);
    4. // 执行第0个
    5. function dispatch(i) {
    6. let fn = middlewares[i];
    7. if (!fn) {
    8. return Promise.resolve();
    9. }
    10. return Promise.resolve(
    11. fn(function next() {
    12. // promise完成后,再执行下一个
    13. return dispatch(i + 1);
    14. })
    15. );
    16. }
    17. };
    18. }
    19. async function fn1(next) {
    20. console.log("fn1");
    21. await next();
    22. console.log("end fn1");
    23. }
    24. async function fn2(next) {
    25. console.log("fn2");
    26. await delay();
    27. await next();
    28. console.log("end fn2");
    29. }
    30. function fn3(next) {
    31. console.log("fn3");
    32. }
    33. function delay() {
    34. return new Promise((reslove, reject) => {
    35. setTimeout(() => {
    36. reslove();
    37. }, 2000);
    38. });
    39. }
    40. const middlewares = [fn1, fn2, fn3];
    41. const finalFn = compose(middlewares);
    42. finalFn();

    compose用在koa中,kkb.js

    1. const http = require("http");
    2. const context = require("./context");
    3. const request = require("./request");
    4. const response = require("./response");
    5. class KKB {
    6. // 初始化中间件数组
    7. constructor() {
    8. this.middlewares = [];
    9. }
    10. listen(...args) {
    11. const server = http.createServer(async (req, res) => {
    12. const ctx = this.createContext(req, res);
    13. // 中间件合成
    14. const fn = this.compose(this.middlewares);
    15. // 执行合成函数并传入上下文
    16. await fn(ctx);
    17. res.end(ctx.body);
    18. });
    19. server.listen(...args);
    20. }
    21. use(middleware) {
    22. // 将中间件加到数组里
    23. this.middlewares.push(middleware);
    24. }
    25. // 合成函数
    26. compose(middlewares) {
    27. return function(ctx) { // 传入上下文
    28. return dispatch(0);
    29. function dispatch(i) {
    30. let fn = middlewares[i];
    31. if (!fn) {
    32. return Promise.resolve();
    33. }
    34. return Promise.resolve(
    35. fn(ctx, function next() {// 将上下文传入中间件,mid(ctx,next)
    36. return dispatch(i + 1);
    37. })
    38. );
    39. }
    40. };
    41. createContext(req, res) {
    42. let ctx = Object.create(context);
    43. ctx.request = Object.create(request);
    44. ctx.response = Object.create(response);
    45. ctx.req = ctx.request.req = req;
    46. ctx.res = ctx.response.res = res;
    47. return ctx;
    48. }
    49. }
    50. module.exports = KKB;

    常见koa中间件的实现
    koa中间件的规范:
    一个async函数
    接收ctx和next两个参数
    任务结束需要执行next

    1. const mid = async (ctx, next) => {
    2. // 来到中间件,洋葱圈左边
    3. next() // 进入其他中间件
    4. // 再次来到中间件,洋葱圈右边
    5. };

    image.png

    1. const Koa = require('./kkb')
    2. const Router = require('./router')
    3. const app = new Koa()
    4. const router = new Router();
    5. router.get('/index', async ctx => { ctx.body = 'index page'; });
    6. // 路由实例输出父中间件 router.routes()
    7. app.use(router.routes());
    1. class Router {
    2. constructor() {
    3. this.stack = [];
    4. }
    5. register(path, methods, middleware) {
    6. let route = {path, methods, middleware}
    7. this.stack.push(route);
    8. }
    9. // 现在只支持get和post,其他的同理
    10. get(path,middleware){
    11. this.register(path, 'get', middleware);
    12. }
    13. post(path,middleware){
    14. this.register(path, 'post', middleware);
    15. }
    16. routes() {
    17. let stock = this.stack;
    18. return async function(ctx, next) {
    19. let currentPath = ctx.url;
    20. let route;
    21. for (let i = 0; i < stock.length; i++) {
    22. let item = stock[i];
    23. if (currentPath === item.path && item.methods.indexOf(ctx.method) >=
    24. 0) {
    25. // 判断path和method
    26. route = item.middleware;
    27. break;
    28. }
    29. }
    30. if (typeof route === 'function') {
    31. route(ctx, next);
    32. return;
    33. }
    34. await next();
    35. };
    36. }
    37. }
    38. module.exports = Router;