读 koa2 源码后的一些思考与实践

更新日期: 2019-12-16阅读: 1.9k标签: koa

什么是 koa2

Nodejs官方api支持的都是callback形式的异步编程模型。问题:callback嵌套问题

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 的实现 通过以上两段代码,会发现下面几个优点

传统的 http 服务想使用 模块化 不是很方便,我们不能在一个服务里面判断所有的请求和一些内容。而 koa2 对模块化提供了更好的帮助

koa2 把 req,res 封装到了 context 中,更简洁而且方便记忆

中间件机制,采用 洋葱模型 ,洋葱模型流程记住一点( 洋葱是先从皮到心,然后从心到皮 ),通过洋葱模型把代码 流程化 ,让 流水线 更加清楚,如果不使用中间件,在 createServer 一条线判断所有逻辑确实不好。

看不到的优点也很多,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源码的想法,下篇文章见。


关于奇舞周刊

《奇舞周刊》是360公司专业前端团队「 奇舞团 」运营的前端技术社区。关注公众号后,直接发送链接到后台即可给我们投稿。

原文:https://mp.weixin.qq.com/s/tSMZLEieDVIP_Kqy78AHHw


链接: https://www.fly63.com/article/detial/6894

KOA2框架原理解析和实现

koa是一个基于node实现的一个新的web框架,它是由express框架的原班人马打造的。它的特点是优雅、简洁、表达力强、自由度高。它更express相比,它是一个更轻量的node框架

koa-easywechat_一个基于koa2的微信开发中间件

koa-easywechat注意:koa-easywechat中间件要写在最前面,也就是要第一个use,因为我在ctx上挂载了一个wechat对象,这个对象实现了大部分的微信接口,这样才能保证开发者在自己的写路由里,获取到ctx.wechat进行自己的业务开发

koa2中间件_深入理解 Koa2 中间件机制

我们知道,Koa 中间件是以级联代码(Cascading) 的方式来执行的。类似于回形针的方式,今天这篇文章就来分析 Koa 的中间件是如何实现级联执行的。在 koa 中,要应用一个中间件,我们使用 app.use()

koajs--基于node.js的下一代web开发框架

Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。 通过利用 async 函数,Koa 帮你丢弃回调函数,并有力地增强错误处理。

node.js中 koa 框架的基本使用方法

安装 koa、简单使用、级联中间件的概念、获取get请求参数、获取post表单数据和文件上传、路由中间件 koa-router

基于Koa(nodejs框架)对json文件进行增删改查

想使用nodejs(koa)搭建一个完整的前后端,完成数据的增删改查,又不想使用数据库,那使用json文件吧。本文介绍了基于koa的json文件的增、删、改、查。

Koa日志中间件封装开发详解

对于一个服务器应用来说,日志的记录是必不可少的,我们需要使用其记录项目程序每天都做了什么,什么时候发生过错误,发生过什么错误等等,便于日后回顾、实时掌握服务器的运行状态,还原问题场景

Koa中间件

Koa 应用程序是一个包含一组中间件函数的对象,它是按照类似堆栈的方式组织和执行的。当一个中间件调用 next() 则该函数暂停并将控制传递给定义的下一个中间件。当在下游没有更多的中间件执行后,堆栈将展开并且每个中间件恢复执行其上游行为

Koa使用koa-multer上传文件(上传限制、错误处理)

上传文件在开发中是很常见的操作,今天我选择使用koa-multer中间件来实现这一功能,除了上传文件外,我还会对文件上传进行限制,以及发生上传错误时的处理。由于原来的 koa-multer 已经停止维护,我们要使用最新的 @koa/multer

从零实现TypeScript版Koa

这篇文章会讲些什么?如何从零开始完成一个涵盖Koa核心功能的Node.js类库,从代码层面解释Koa一些代码写法的原因:如中间件为什么必须调用next函数、ctx是怎么来的和一个请求是什么关系

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!