koa2的特点优势

什么是 koa2

  1. Nodejs官方api支持的都是callback形式的异步编程模型。问题:callback嵌套问题
  2. koa2 是由 Express原班人马打造的,是现在比较流行的基于Node.js平台的web开发框架,Koa 把 Express 中内置的 router、view 等功能都移除了,使得框架本身更轻量,而且扩展性很强。使用koa编写web应用,可以免除重复繁琐的回调函数。

koa2 的优点

优点这个东西,我直接说它多好,你可能又不开心,但是我们可以对比哦!这里我只说它对比原生的 Node.js开启 http 服务 带来了哪些优点!

  • 先看一下原生 Node.js 我开启一个 http 服务
const http = require('http');http.createServer((req,res)=>{    res.writeHead(200);    res.end('hi koala');}).listen(3000);
  • 看一下使用 koa2 开启一个http 服务
const Koa = require('koa') ;const app = new Koa();const {createReadStream} = require('fs');app.use(async (ctx,next)=>{    if(ctx.path === '/favicon.ico'){        ctx.body = createReadStream('./avicon.ico')    }else{        await next();    }});app.use(ctx=>{    ctx.body = 'hi koala';})app.listen(3000);

我在 koa2 中添加了一个判断 /favicon.ico 的实现 通过以上两段代码,会发现下面几个优点

  1. 传统的 http 服务想使用模块化不是很方便,我们不能在一个服务里面判断所有的请求和一些内容。而 koa2 对模块化提供了更好的帮助
  2. koa2 把 req,res 封装到了 context 中,更简洁而且方便记忆
  3. 中间件机制,采用洋葱模型,洋葱模型流程记住一点(洋葱是先从皮到心,然后从心到皮),通过洋葱模型把代码流程化,让流水线更加清楚,如果不使用中间件,在 createServer 一条线判断所有逻辑确实不好。
  4. 看不到的优点也很多,error 错误处理,res的封装处理等。

自己实现一个koa2

在实现的过程中会我看看可以学到那些知识

listen 函数简单封装

koa2 直接使用的时候,我们通过 const app = new Koa();,koa 应该是一个类,而且可以直接调用 listen 函数,并且没有暴漏出http 服务的创建,说明在listen函数中可能创建了服务。到此简单代码实现应该是这样的:

class Kkb{    constructor(){        this.middlewares = [];    }    listen(...args){        http.createServer(async (req,res)=>{                    // 给用户返回信息         this.callback(req,res);         res.writeHead(200);         res.statusCode = 200;         res.end('hello koala')        }).listen(...args)    }}module.exports = Kkb;

实现 context 的封装

实现了简单 listen 后,会发现回调函数返回的还是 req 和 res ,要是将二者封装到 context 一次返回就更好了!我们继续

 const ctx = this.createContext(req,res);

看一下 createContext 的具体实现

const request = require('./lib/request');const response = require('./lib/response');const context = require('./lib/context'); createContext(req,res){                // 创建一个新对象,继承导入的context        const ctx = Object.create(context);        ctx.request = Object.create(request);        ctx.response = Object.create(response);        // 这里的两等于判断,让使用者既可以直接使用ctx,也可以使用原生的内容        ctx.req = ctx.request.req = req;        ctx.res = ctx.response.res = res;        return ctx;    }

context.js

module.exports = {    get url(){        return this.request.url;    },    get body(){        return this.response.body;    },    set body(val){        this.response.body = val;    }}

request.js

module.exports = {    get url(){        return this.req.url;    }}

这里在写 context.js 时候,用到了set 与 get 函数,get 语句作为函数绑定在对象的属性上,当访问该属性时调用该函数。set 语法可以将一个函数绑定在当前对象的指定属性上,当那个属性被赋值时,你所绑定的函数就会被调用。

实现洋葱模型

compose 另一个应用场景

说洋葱模型之前先看一个函数式编程内容:compose 函数前端用过 redux 的同学肯定都很熟悉。redux 通过compose来处理 中间件 。原理是 借助数组的 reduce 对数组的参数进行迭代

// redux 中的 compose 函数export default function compose(...funcs) {  if (funcs.length === 0) {    return arg => arg  }  if (funcs.length === 1) {    return funcs[0]  }  return funcs.reduce((a, b) => (...args) => a(b(...args)))}

洋葱模型实现

再看文章开头 koa2 创建 http 服务函数,会发现多次调用 use 函数,其实这就是洋葱模型的应用。

洋葱是由很多层组成的,你可以把每个中间件看作洋葱里的一层,根据app.use的调用顺序中间件由外层到里层组成了整个洋葱,整个中间件执行过程相当于由外到内再到外地穿透整个洋葱

引用一张著名的洋葱模型图:

每次执行 use 函数,我们实际是往一个函数数组中添加了一个函数,然后再次通过一个 compose 函数,处理添加进来函数的执行顺序,也就是这个 compose 函数实现了洋葱模型机制。

具体代码实现如下:

// 其中包含一个递归 compose(middlewares){        return async 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)                    })                )            }        }    }

首先执行一次 dispatch(0) 也就是默认返回第一个 app.use 传入的函数 使用 Promise 函数封装返回,其中第一个参数是我们常用的 ctx,

第二个参数就是 next 参数,next 每次执行之后都会等于下一个中间件函数,如果下一个中间件函数不为真则返回一个成功的 Promise。因此我们每次调用 next() 就是在执行下一个中间件函数。

来试试我们自己实现的koa2

使用一下我们自己的 koa2 吧,用它做一道常考洋葱模型面试题,我想文章如果懂了,输出结果应该不会错了,自己试一下!

const KKB = require('./kkb');const app = new KKB();app.use(async (ctx,next)=>{    ctx.body = '1';    await next();    ctx.body += '3';})app.use(async (ctx,next)=>{    ctx.body += '4';    await delay();    await next();    ctx.body += '5';})app.use(async (ctx,next)=>{    ctx.body += '6'})async function delay(){    return new Promise((reslove,reject)=>{        setTimeout(()=>{            reslove();        },1000);    })}app.listen(3000);

解题思路:还是洋葱思想,洋葱是先从皮到心,然后从心到皮

答案: 1 4 6 5 3

补充与说明

本文目的主要是让大家学到一个koa2的基本流程,简单实现koa2,再去读源码有一个清晰的思路。实际源码中还有很多优秀的值得我们学习的点,接下来再列举一个我觉得它很优秀的点——错误处理,大家可在原有基础上继续实现,也可以去读源码继续看!加油加油

源码中 koa 继承自 Emiiter,为了处理可能在任意时间抛出的异常所以订阅了 error 事件。error 处理有两个层面,一个是 app 层面全局的(主要负责 log),另一个是一次响应过程中的 error 处理(主要决定响应的结果),koa 有一个默认 app-level 的 onerror 事件,用来输出错误日志。

 // 在调用洋葱模型函数后面,koa 会挂载一个默认的错误处理【运行时确定异常处理】    if (!this.listenerCount("error")) this.on("error", this.onerror);
  onerror(err) {    if (!(err instanceof Error))      throw new TypeError(util.format("non-error thrown: %j", err));    if (404 == err.status || err.expose) return;    if (this.silent) return;    const msg = err.stack || err.toString();    console.error();    console.error(msg.replace(/^/gm, "  "));    console.error();  }

通过 Emiiter 实现了错误打印,Emiiter 采用了发布订阅的设计模式,如果有对 Emiiter 有不太清楚的小伙伴可以看我这篇文章

[源码解读]一文彻底搞懂Events模块

总结

本文注重思想,精简版本,代码与实现都很简单。封装,递归,设计模式都说了一丢丢,希望也能对你有一丢丢的提升和让你去看一下koa2源码的想法,下篇文章见。

▼ 原创系列推荐▼

TypeScript真香系列——接口篇

消息队列助你成为高薪 Node.js 工程师

深入理解Node.js 进程与线程(8000长文彻底搞懂)

[源码解读]一文彻底搞懂Events模块

Node.js 高级进阶之 fs 文件模块学习

Node进阶-探究不在V8堆内存中存储的Buffer对象

说Node.js做后端开发,stream有必要了解下

在看,分享给身边的开发

koa2异常处理_读 koa2 源码后的一些思考与实践相关推荐

  1. java 事件分发机制_读Android源码之事件分发机制最全总结

    原标题:读Android源码之事件分发机制最全总结 本文源码来自andorid sdk 22,不同版本会有细微差别,但核心机制是一致的 一.概述 事件分发有多种类型, 本文主要介绍Touch相关的事件 ...

  2. 读Zepto源码之操作DOM

    2019独角兽企业重金招聘Python工程师标准>>> 这篇依然是跟 dom 相关的方法,侧重点是操作 dom 的方法. 读Zepto源码系列文章已经放到了github上,欢迎sta ...

  3. 读Lodash源码——chunk.js

    The time is out of joint: O cursed spite, That ever I was born to set it right. --莎士比亚 最艰难的第一步 最近学习遇 ...

  4. 读 zepto 源码之工具函数

    对角另一面 读 zepto 源码之工具函数 Zepto 提供了丰富的工具函数,下面来一一解读. 源码版本 本文阅读的源码为 zepto1.2.0 $.extend $.extend 方法可以用来扩展目 ...

  5. 读Zepto源码之Ajax模块 1

    Ajax 模块也是经常会用到的模块,Ajax 模块中包含了 jsonp 的现实,和 XMLHttpRequest 的封装. 读 Zepto 源码系列文章已经放到了github上,欢迎star: rea ...

  6. 带着问题读 TiDB 源码:Power BI Desktop 以 MySQL 驱动连接 TiDB 报错

    原文来源: https://tidb.net/blog/d343818b 作者:张翔 常有人说,阅读源码是每个优秀开发工程师的必经之路,但是在面对像类似 TiDB 这样复杂的系统时,源码阅读是一个非常 ...

  7. zepto ajax php实例,读Zepto源码之Ajax模块

    Ajax 模块也是经常会用到的模块,Ajax 模块中包含了 jsonp 的现实,和 XMLHttpRequest 的封装. 读 Zepto 源码系列文章已经放到了github上,欢迎star: rea ...

  8. 试读angular源码第三章:初始化zone

    直接看人话总结 前言 承接上一章 项目地址 文章地址 angular 版本:8.0.0-rc.4 欢迎看看我的类angular框架 文章列表 试读angular源码第一章:开场与platformBro ...

  9. 「读懂源码系列2」我从 lodash 源码中学到的几个知识点

    前言 上一篇文章 「前端面试题系列8」数组去重(10 种浓缩版) 的最后,简单介绍了 lodash 中的数组去重方法 _.uniq,它可以实现我们日常工作中的去重需求,能够去重 NaN,并保留 {.. ...

最新文章

  1. Django高级管理静态文件和中间件5.1
  2. Python大神用的贼溜,9个实用技巧分享给你
  3. WebStorm 使用经验
  4. json 和 jsonp
  5. 相同类方法之间调用,注解失效的问题
  6. 信息学奥赛一本通(1043:整数大小比较)
  7. [swift] LeetCode 96. Unique Binary Search Trees
  8. Python basemap模拟导弹发射
  9. Latex 图像、表格标题(题注)加脚注
  10. 3 万亿美元,苹果问鼎全球市值最高公司宝座
  11. Eclipse如何使用git上传项目到G码云
  12. Houdini+UE4制作好看的地形(材质篇)
  13. 网络计算机干啥用,路由器干什么用?路由器功能详解【图文】
  14. 在下载 chromium 源码时错误汇总
  15. 云计算和计算机应用的区别,普适计算与云计算的区别
  16. python获取股票的市盈率_如何利用python获取股票行情信息?
  17. 互联网运营面试题_产品运营面试常见问题
  18. IDEA最新安装插件方法,最简单
  19. 财富500强排名(2010)
  20. 议题征集令 | Apache DolphinScheduler Meetup 2021 来啦,议题征集正式开启!

热门文章

  1. 在VI中删除行尾的换行符
  2. CodeForces - 786BLegacy——线段树建图+最短路
  3. 剑指Offer09. 用两个栈实现队列
  4. 配置独立于系统的PYTHON环境
  5. 第4章 springboot热部署 4-1 SpringBoot 使用devtools进行热部署
  6. logstash windows
  7. CAS的ABA问题描述 AtomicStampReference
  8. 多米诺骨牌v.1MEL语言
  9. ZooKeeper編程01--RMI服務的多服務器管理
  10. 与图论的邂逅05:最近公共祖先LCA