前言Koa是一个运行在Node.js中的Web服务框架,小而美。Koa2是Koa框架的最新版本,Koa3还没有正式推出,Koa1正在被替换的路上。Koa2和Koa1最大的区别是Koa1基于co来管理Promise/Generator中间件,而Koa2紧跟最新的ES规范,支持AsyncFunction(Koa1不支持)。两者的中间件模型行为相同,但底层语法不同。Koa2正在蚕食Express的市场份额。最大的原因是Javascript语言特性的进化和ChromeV8引擎的升级,赋予Node.js更大的能力,提升开发者的编程体验,满足开发者灵活定制场景和性能提升的需求,蚕食是理所当然的事。从2018年开始,Koa2将超越Express成为今年最流行的Node.js框架。以上就是Koa2的现状和趋势。从2018年来看,学习Koa2的大潮已经到来。想要精通Koa2,需要了解关于它的哪些知识?这些知识类似于Node.js和语言规范有什么关系,它的内部组成是什么,运行机制是什么,是否难于定制和扩展,它的三方库生态如何,有哪些应用场景,如何与前端结合等,这些问题将在本文中进行简单的讨论,Koa2详细的代码示例和深度解析都在这里。备注:下面提到的Koa是指Koa2.x版本。作者TJ知道TJ的童鞋们。他推动着整个Node.js/NPM社区都在长足进步,被称为大神也不为过,但是大神的脑回路向来就和常人不一样。关于大神的传说很多。最有意思的是,在国外著名程序员论坛reddit上,有人说TJ从来都不是一个人。一个人能有如此高效疯狂的代码输出,着实令人震惊。他背后一定有一个团队,因为他从来不参加技术会议,也没见过任何人,最后TJ离开了Node社区去围棋,这种做事方式很谷歌,所以TJ是一个招牌对于Google,大家众说纷纭,吵得不可开交,但有一点大家是一致的,那就是非常肯定和感谢他对Nodejs社区的贡献和付出。Express架构和中间件模型在讲Koa之前,我们先来比较一下Express。在Express中,不同时期的代码组织方式大不相同。后期做了很多拆分,大部分模块分离出来自己官方维护,或者使用社区其他开发者提供的中间件模块。不过纵观Express多年的历史,还是比较庞大和完备的,API框架也比较丰富,而且它的整个中间件模型都是基于callback回调的,callback常年饱受诟病。对于一个web服务框架来说,它的核心流程是在HTTP入口到流出的整个过程中,从它的流入数据中收集需要的参数材料,然后把需要的材料附加到流出数据结构中,无论是静态文件还是静态文件JSON数据,并且在收集和追加的过程中,需要各种中间件巨头参与,有的在做日志记录,有的在做表单解析,有的在做管理会话。因为你是老板,所以你的脾气通常很差。如果你不安排他们的注册顺序,通过机制管理他们的进出顺序,他们不仅不会配合好,还可能毁了你的名额。所以在Express中,首先是对HTTP这个大佬的管理(其他协议先不涉及)。为了对付这个大家伙,快递牺牲了三件,哦不,居然是四件法宝。首先是通过express()得到的服务器的整个运行实例。这个实例相当于一个酒店,你是来访的客人-HTTP请求。酒店负责满足您的所有需求,让您满意。酒店里,有两个工作人员,一个是req(请求),负责接待你,叫阿来,一个是狠角色,会送你走——res(response),叫阿来,阿来会接待你进入酒店,门口的摄像头会给你拍照(Log记录进出时间,你的特征),采集你的指纹(老会员识别),然后带你去前台签到(获取你的需求,比如你要带走你的随身物品)一套),然后酒店安排你在房间休息(等待回应),各个后勤人员忙着接待不同的客人,其中之一是帮助您拿起西装。捡起来后,交给阿来。阿来会把西装再给你穿上,也可能会帮你装饰一下,比如给你带个帽子(加个定制头),然后送你出去,门口的摄像头会拍下你,你会知道酒店服务时间。。。实在是编不下去了。用物理世界的案例来对应程序世界是相当困难的,严谨性也不够,但还是希望能帮助新手同学留下深刻的印象。Express源码简单分析了上面酒店的四大法宝,其实就是server运行实例,req请求对象,res响应对象,middleware中间件。负责拍照、打卡、分析需求的其实都是中间件,一个一个过滤,他们按照自己的规则收集、分析、转换和追加,把这个HTTP客人从头到脚掐死,客人舒舒服服地离开。中间件是很多web框架中比较核心的概念。它们可以根据不同的场景集成到框架中,以增强框架的服务能力。框架需要提供一套机制来保证中间件的有序执行。这种机制在不同的框架中是非常不同的。在Express中,我们一一使用use(middlewares())。使用顺序和使用规则由快递公司自行控制。在express/express.js中,服务端通过handle运行实例app,将Nodejs的req和res传递给handle函数,让handle控制内部对象:app=function(req,res,next){app.handle(req,res,next)}在express/application.js中,拿到控制权的handle继续将请求响应和回调分配给express的核心路由模块,即router:app.handle=函数句柄(请求,资源,回调){varrouter=this._routervardone=callback||finalhandler(req,res,{env:this.get('env'),onerror:logerror.bind(this)})路由器。handle(req,res,done)}这里的router.handle保存着req,res对象。可以理解为express将Nodejs监听的请求的三个元素(req,res,cb)委托给了内部的路由模块router。然后再回到justuse(middlewares(),Express每次使用中间件的时候,也会把这个中间件交给router:app.use=functionuse(fn){router.use('/',fn)}andInrouter,有一个很重要的概念,就是layer层,可以理解为中间件层层叠叠,一层层堆叠:varlayer=newLayer(path,{sensitive:this.caseSensitive,strict:false,end:false},fn)this.stack.push(layer)上面是伪代码(大部分已经删掉),可以看成是在开始运行的时候表达注册一个中间件函数栈,栈到be被调用的中间件,一旦请求进来,就会被路由句柄处理:proto.handle=functionhandle(req,res,out){next()functionnext(err){varlayervarrouteself.process_params(layer,paramcalled,req,res,function(err){if(route){returnlayer.handle_request(req,res,next)}trim_prefix(layer,layerError,layerPath,path)})}functiontrim_prefix(层,layerError,layerPath,path){if(layerError){layer.handle_error(layerError,req,res,next)}else{layer.handle_request(req,res,next)}}}handle中的next是转整个的关键middlewarestack,在所有的中间件中,这个next是必须要执行的,这样当前控制权以回调的形式往下走但是问题是,这个机制开始的时候,如果没有事件配合,很难原路走,然后又原路返回,相当于每个中间件来回过滤了两次,给中间件这就是Express受限的地方,也是Express市场会被Koa蚕食的重要原因。具体的Express代码比这里描述的要复杂好几倍。有兴趣的可以去看源码,应该会有更多收获。如果没有Koa这样的框架,Express内部实现绝对不为过。只是这种相对复杂的内部中间件机制未必符合大家的口味,也说明了早些年由于JS的能力有限,做一些流程的双向控制是多么的困难。Express的分析就到此为止。这不是本文的重点。了解其内在的复杂性,体会到它的微妙复杂就足够了,因为它是特定历史阶段的历史产物,有着特定的历史使命。早期的Koa模型——我们不一样得益于大神非凡的脑回路,Koa从一开始就选择了与Express截然不同的架构方向。上面Express的部分你不理解也没关系,因为Koa在这里的处理,会让你的脑回路瞬间清晰。首先你要明白,Koa和Express是同一个东西的不同实现,所以也就意味着它们对外提供的大部分能力都是一样的。太保有很大的能力。首先,从newKoa()运行的服务器实例就像一只青蛙,张开嘴要吞下所有的请求。通过它,服务才能真正运行起来。和Express一样,这会被跳过。关键是它的上下文,也就是ctx。这个产品有很多参考资料,核心是request和response。这两个可以对应Express的两个对立的req和res。在Koa中,两者都集中在ctx内部进行管理,分别通过ctx.request和ctx.response直接访问。以前用两个独立的Express对象来做的事情,现在一个ctx就够了。上下文对象都在他手里,想联系谁就联系谁。二是它的中间件机制,Koa真正的魅力,先看代码:constKoa=require('koa')constapp=newKoa()constindent=(n)=>newArray(n).join('')constmid1=()=>async(ctx,next)=>{ctx.body=`
request=>第一层中间件
`awaitnext()ctx.body+=`
Response<=第一层中间件
`}constmid2=()=>async(ctx,next)=>{ctx.body+=`
${indent(4)}Request=>SecondLayerMiddleware
`awaitnext()ctx.body+=`
${indent(4)}Response<=SecondLayerMiddleware
`}app.use(mid1())app.use(mid2())app.use(async(ctx,next)=>{ctx.body+=`
${indent(12)}=>Koa核心处理业务<=`})app.listen(2333)可以运行这22行代码,在浏览器中访问localhost:2333可以看到代码的执行路径,一个HTTP请求,从入口到流出,有两个穿透,每个中间件被穿透两次。这个顺序的正向进入和反向穿透不是强制性的,而是Koa的能力可以轻松拥有。同样的Ability,但是在Express中实现起来非常困难。Koa2源码简析要理解上面提到的能力,需要看一下Koa的核心代码:同样的app.use(middlewares()),在koa/application.js中,每个中间件也被推入到一个中array:use(fn){this.middleware.push(fn)}服务器启动时,设置监听器,同时注册回调函数:listen(...args){server=http.createServer(this.callback()).listen(...args)}回调函数返回(req,res)给Node.js接收请求,在里面首先根据req,res创建ctx,也就是可以管理请求的同时和response的家伙,重点是上面压入数组的middlewares经过compose处理,然后抛给handleRequest:callback(){constfn=compose(this.middleware)returnhandleRequest=(req,res)=>{constctx=this.createContext(req,res)returnthis.handleRequest(ctx,fn)}}compose即koa-compose,简单理解为通过它递归实现Promise的链式执行众所周知,async函数本质上会返回一个Promise。这里先不说compose。继续看handleRequest:handleRequest(ctx,fnMiddleware){returnfnMiddleware(ctx).then(respond(ctx))}确实简洁,功能也不强大。send,请求进来后,会执行所有可以递归调用的中间件数组,每个中间件都可以拿到ctx。同时由于asyncfunction的语法特点,在中间件Middleware中可以把执行权交给后者,这样一层层交给,最后一层层往回执行,这样实现了请求从一条路径进入,响应从同一路径返回的效果。借用官方文档的一张图来表达这个过程:我知道这张图不够用,再奉上第二张官方图,大名鼎鼎的洋葱模型:向Koa2学习什么从上面的对比中,我们其实发现了Koa2的独特魅力,这些魅力一方面与框架设计理念有关,另一方面与语言特性有关。语言特性无非就是以下几点:箭头函数Promise规范iteratorgeneratorfunction执行原理异步函数AsyncFunction和Koa2ctx常用的API(也就是它的能力),koa-compose工具函数的递归特性,中间件执行顺序和用法是基础,值得学习,而且这些知识和语言规范有非常密切的关系,所以意味着学完这些,还需要去ES6/7/8选择更多的语法特性,进入早点学坑。限于篇幅,本文不展开讨论。如果你有兴趣学习上面的基础知识,可以关注Koa2解读+数据抓取+实战电影网站,了解更多实战姿势。Koa2和Express如何选择?能聊得开心吗?其实没关系,选Koa2,都2018了,别等了。同时,它必须是必要的。事实上,它不是。我们可以更客观的看待选型问题,再梳理一下思路:Koa基于新的语法特性,实现Promise链式交付,对错误处理更友好。Koa不绑定任何中间体,它是一个干净的裸框架,你可以添加任何你需要的东西。Koa对流媒体有很好的支持。上下文对象的交叉引用使得内部流程和请求响应更加紧凑。如果说Express是大而全,那么Koa则是小而精。两者的定位不同,但是Koa的扩展性非常好。一些中间件的少量组装可以立即与Express相媲美。代码质量也更高,设计理念更先进,语法特性更先进。这是站在用户的角度进行比较的结果。从内部实现来看,Koa的中间件加载和执行机制与Express完全不同。两者的巨大差异也导致了一个项目可以完全走向两种不同的中间件设计和实现方式,但是我们作为框架用户和业务开发者往往会用到他们,所以对于Nodejs用户,Express可以满足你,Koa可以满足你,Express让你很酷,Koa让你更优秀。这就是为什么,阿里的企业级框架Eggjs底层是Koa而不是Express,360大而全的thinkjs底层也是Koa,包括沃尔玛的hapi。虽然没有用到Koa,但是他的核心开发者写了一篇博客说,在Koa的冲击和影响下,升级到asyncfunction和跟上语法是必要的,而这些都是Koa已经做到的全部基础,并且任何上层结构都变得更简单。当你选择Express时,或者当你从Express升级到Koa时,你不必太担心。只要成本允许,就可以用。如果实施成本太高,可以使用Express。遇到其他新项目,没有历史包袱,用Koa也不迟。Koa运行机制和Nodejs事件循环其实通过上面的页面,我们对内部构成有了基本的了解,运行机制其实就是中间件执行机制,而自定义的扩展性,我们上面提到的Eggjs和Thinkjs已经充分证明了可以定制强大的潜力,这里主要说一下运行机制,一个是Koajs本身,一个是通过它下到Node.js的底层,它的运行机制是什么,这就涉及到了Libuv的事件循环,如果你不知道它,就很难在Node.js技能树中提升一个级别,所以它也很重要。Libuv的事件循环本质上决定了Node.js的异步属性和异步能力。说到异步,大家都知道Node.js的异步非阻塞IO,但是对于同步异步和阻塞非阻塞,每个人都有自己的想法。说到异步IO,其实我们经常说的是操作系统提供的异步IO能力。首先,什么是IO?把它想象成Input,也就是输入。对应于主机,会有一个物理接口用于流入数据或信号。显示器作为视觉外设,对应主机时会有专门的接口用于输出数据。这就是生活。我们在接口中可以看到的IO能力,这个接口会下到操作系统层面。在操作系统层面,它会提供很多能力,比如磁盘读写、DNS查询、数据库连接、网络请求接收和返回等,在不同的操作系统中,它们的特性也不一致,有的纯粹是异步的,非阻塞的,有的是同步阻塞的,不管怎样,我们可以把这些IO看做是上层应用和下层系统之间的。上层依赖于下层,上层可以进一步定制这些能力。如果这个交互是异步非阻塞的,那么这就是一个异步IO模型。如果是同步和阻塞,那么就是同步IO。模型。在Nodejs中,我们可以以文件读写为例。Koa只是一个上层的Web应用服务框架。它所有与操作系统home的通信能力都是基于Node.js的整个通信服务模型,Nodejs提供了模块文件系统,也就是fs,它提供了读写文件的接口,比如异步接口readFile,是一个典型的异步IO接口,而readFileSync是一个阻塞同步IO接口。以此类推,我们站在上层Web服务的层面,就很容易理解Node.js的异步非阻塞模型和异步IO能力。那么Node.js的异步能力是建立在Libuv层的几个阶段,什么?还有阶段吗?是的,除了解释执行JS代码的ChromeV8虚拟机外,Node.js底层就是Libuv,它和操作系统交互,封装了很多不同平台的接口,相当于把操作系统打通了.异步差异带来的兼容性让Node.js可以对外提供一致的同步和异步API,而Libuv的几个stage是对单线程JS最有利的辅助实现。所有的异步都可以看作是任务。任务是耗时的,libuv将这些任务分为不同的类型和阶段,有自己的执行规则和执行优先级。可以先预测下面代码的执行结果:constEventEmitter=require('events')classEEextendsEventEmitter{}constyy=newEE()yy.on('event',()=>console.log('这是个大问题'))setTimeout(()=>console.log('0毫秒后超时的定时器回调'),0)setTimeout(()=>console.log('100毫秒后超时的定时器回调')定时器回调'),100)setImmediate(()=>console.log('立即立即回调'))process.nextTick(()=>console.log('process.nextTickcallback'))Promise.resolve().then(()=>{yy.emit('event')process.nextTick(()=>console.log('process.nextTickcallback'))console.log('promisefirstcallback')}).then(()=>console.log('promisesecondcallback'))你会发现你已经步入了一个[美丽]的世界,这就是我们在Koa之后所知道的,如果我们想继续学习,需要的知识掌握了,这块知识才是真正的干货,真的很难三言两语说清楚,我们保持思路继续前行。Koa2的三方库生态如何?在Koa1时代,Koa2刚出来的时候,三方库确实不多,需要自己打包。甚至还有专门做这个工作的koa-convert,将第一代koa中间件转换成兼容第二代koa的Compatible形式。但是在今天,Koa2的生态已经相当完善了,尤其是2018年随着越来越多的开发者进入Koa2,将会有大量业界领先的模块库进入Koa2的大池子,你会发现你可以有更多而且选择多了,所以他的生态是没问题的。到此如何与前端结合,本文即将结束,感觉自己还有很多话要说,但写下来又怕是三千尺。我想一句话回答这个问题:小而美是每个工程师最终都会选择修身,Koa2是小而美,能和它结合的一定也是小而美,所以2018年,Parcel非Parcel莫属,小而美,Parcel如何与AntDesign/React/Bootstrap等前端框架库结合使用,可以关注Koa2解读+数据抓取+实战电影网站以了解更多姿势。回到本文标题:Koa2取代Express还有多久?我认为完全取代它是不可能的,但是使用Koa2(以及基于它的框架)的新项目会在数量上碾压Express。时间,2018-2019够了,所以从2018年开始,但请不要落后,加油!封面图片来自codetrick