Koa2 还有多久取代 Express

前言

Koa 是运行在 Node.js 中的 web 服务框架,小而美。html

Koa2 是 Koa 框架的最新版本,Koa3 尚未正式推出,Koa1 正走在被替换的路上。前端

Koa2 与 Koa1 的最大不一样,在于 Koa1 基于 co 管理 Promise/Generator 中间件,而 Koa2 紧跟最新的 ES 规范,支持到了 Async Function(Koa1 不支持),二者中间件模型表现一致,只是语法底层不一样。node

Koa2 正在蚕食 Express 的市场份额,最大的缘由是 Javascript 的语言特性进化,以及 Chrome V8 引擎的升级,赋予了 Node.js 更大的能力,提高开发者的编程体验,知足开发者灵活定制的场景以及对于性能提高的需求,蚕食也就水到渠成,2018 年开始,Koa2 会超越 Express 成为本年最大普及量的 Node.js 框架。程序员

以上就是 Koa2 的现状,以及它的趋势,站在 2018 年的节点来看,Koa2 的学习大潮已经到来,那么若是要掌握 Koa2,须要去学习它的哪些知识呢,这些知识跟 Node.js 以及语言规范有什么关系,它的内部组成是如何的,运行机制怎样,定制拓展是否困难,以及它的三方库生态如何,应用场景有哪些,跟前端有如何结合等等,这些问题本文将作简要的探讨,Koa2 详细的代码案例和深度剖析见这里web

备注:以下提到的 Koa 均指代 Koa 2.x 版本

关于做者 TJ

了解过 TJ 的童鞋都知道,他以惊为天人的代码贡献速度、源源不断的开发热情和巧夺天工的编程模型而推进整个 Node.js/NPM 社区大步迈进,称为大神绝不过度,而大神的脑回路,向来与凡人不一样。数据库

关于大神的传说有不少,最有意思的是在国外著名程序员论坛 reddit 上,有人说,TJ 历来就不是一我的,一我的能有这么高效而疯狂的代码产出实在是太让人震惊了,他背后必定是一个团队,由于他历来都不参加技术会议,也不见任何人,而最后 TJ 离开 Node 社区去转向 Go,这种作事方式很是谷歌,因此 TJ 是谷歌的一个招牌,你们众说纷纭,吵的不可开交,不过有一点你们都是达成共识的,那就是很是确定和感谢他对于 Nodejs 社区的贡献和付出。express

Express 的架构和中间件模型

聊 Koa 以前,先对比下 Express,在 Express 里面,不一样时期的代码组织方式虽然大为不一样,好比早期是全家桶各类路由、表单解析都囊括到一个项目中,中后期作了大量的拆分,将大部分模块都独立出来官方自行维护,或者是采用社区其余开发者提供的中间件模块,但纵观 Express 多年的历程,他依然是相对大而全,API 较为丰富的框架,而且它的整个中间件模型是基于 callback 回调,而 callback 常年被人诟病。编程

对于一个 web 服务框架来讲,它的核心流程,就是在整个 HTTP 进入到流出的过程当中,从它的流入数据上采集所须要的参数素材,再向流出的数据结构上附加指望素材,不管是一个静态文件仍是 JSON 数据,而在采集和附加的过程当中,须要各个中间件大佬的参与,有的干的是记录日志的活儿,有的干的是解析表单的活儿,有的则是管理会话,既然是大佬,通常都脾气大,你不安排好他们的注册顺序,不经过一种机制管理他们的入场退场顺序,他们不只很差好配合,还可能砸了你的场子。api

那么 Express 里面,首先就是对于 HTTP 这个你们伙的管理(其余协议先不涉及),管理这个你们伙,Express 祭出了三件,哦不,实际上是四件法宝。
首先是经过 express() 拿到的整个服务器运行实例,这个实例至关因而一个酒店,而你就是来访的客人 - HTTP 请求,酒店负责你一切需求,作到你满意。
在酒店里面,还有两个工做人员,一个是 req(request) 负责接待你的叫阿来吧,还有一个送你离开的狠角色 - res(response),叫阿去吧,阿来接待到你进酒店,门口的摄像头会你拍照(Log 记录来去时间,你的特征),收集你的指纹(老会员识别),引领你去前台签到(获取你的需求,好比你要拿走属于你的一套西服),而后酒店安排你到房间休息(等待响应),里面各类后勤人员忙忙碌碌接待不一样的客人,其中有一个是帮你取西服的,取了后,交给阿来,阿来再把西服穿你身上,同时还可能帮你装饰一番,好比给你带个帽子(加个自定义头),而后送你出门,门口的摄像头还会拍你一下,就知道了酒店服务你的时间......实在编不下去了,想用物理世界的案例来对应到程序世界是蛮难的,严谨度不够,不过帮新手同窗留下一个深入印象却是可取的。数组

Express 源码简要分析

上面酒店的 4 件法宝,其实就是服务器运行实例,req 请求对象,res 响应对象和中间件 middlewares,刚才负责照相的,签到的,分析需求的其实都是中间件,一个一个滤过去,他们根据本身的规则进行采集、分析、转化和附加,把这个 HTTP 客人,从头到脚捏一遍,客人就舒舒服服的离开了。

中间件是众多 web 框架中比较核心的概念,它们能够根据不一样的场景,来集成到框架中,加强框架的服务能力,而框架则须要提供一套机制来保证中间件是有序执行,这个机制在不一样的框架中则大为不一样,在 Express 里面,咱们经过 use(middlewares()) 逐个 use 下去,use 的顺序和规则都由 express 自身控制。
在 express/express.js 中,服务器运行实例 app 经过 handle 来把 Nodejs 的 req 和 res 传递给 handle 函数,赋予 handle 对于内部对象的控制权:

app = function(req, res, next) {
  app.handle(req, res, next)
}

而在 express/application.js 中,拿到控制权的 handle 又把请求响应和回调,继续分派给了 express 的核心路由模块,也就是 router:

app.handle = function handle (req, res, callback) {
  var router = this._router
  var done = callback || finalhandler(req, res, {
    env: this.get('env'),
    onerror: logerror.bind(this)
  })
  router.handle(req, res, done)
}

这里的 router.handle 就持有到了 req, res 对象,能够理解为,express 把 Nodejs 监听到的请求三要素(req, res, cb) 下放给了内部的路由模块 router。
而后继续回到刚才 use(middlewares(),Express 每一次 use 中间件,都会把这个中间件也交给 router:

app.use = function use(fn) {
  router.use('/', fn)
}

而 router 里面,有很重要一个概念,就是 layer 层,能够理解为中间件堆叠的层,一层层堆叠起来:

var layer = new Layer(path, {
  sensitive: this.caseSensitive,
  strict: false,
  end: false
}, fn)
this.stack.push(layer)

以上是伪代码(删减了大部分),能够看作是 express 在启动运行的时候,注册好了一个中间件函数栈,里面堆叠好了待被调用的中间件,一旦请求进来,就会被 router handle 来处理:

proto.handle = function handle(req, res, out) {
  next()
  function next(err) {
    var layer
    var route
    self.process_params(layer, paramcalled, req, res, function (err) {
      if (route) {
        return layer.handle_request(req, res, next)
      }
      trim_prefix(layer, layerError, layerPath, path)
    })
  }
  function trim_prefix(layer, layerError, layerPath, path) {
    if (layerError) {
      layer.handle_error(layerError, req, res, next)
    } else {
      layer.handle_request(req, res, next)
    }
  }
}

handle 里面的 next 是整个中间件栈可以转起来的关键,在全部的中间件里面,都要执行这个 next,从而把当前的控制权以回调的方式往下面传递。
可是问题就是这种机制在最初的时候,若是没有事件的配合,是很难作到原路进去,再顺着原路回去,至关因而每一个中间件都被来回滤了 2 遍,赋予中间件更灵活的控制权,这就是掣肘 Express 的地方,也是 Express 市场必定会被 Koa 蚕食的重要缘由。

具体 Express 的代码比这里描述的要复杂好几倍,你们有兴趣能够去看源码,应该会有更多的收获,若是没有 Koa 这种框架存在的话,Express 的内部实现用精妙形容绝对不为过,只是这种相对复杂一些的内部中间件机制,未必符合全部人的口味,也说明了早些年限于 JS 的能力,想要作一些流程双向控制多么困难。
关于 Express 就分析到这里,这不是本文的重点,了解它内部的复杂度以及精妙而复杂都实现就能够了,由于这是特定历史阶段的历史产物,有它特定的历史使命。

早期的 Koa 模型 - 咱们不同

得益于大神非同寻常的脑回路,Koa 从一开始就选择了跟 Express 彻底不一样的架构方向,上面 Express 的部分你们没看懂也不要紧,由于 Koa 这里的处理,会让你瞬间脑回路清晰。

首先要明白,Koa 与 Express 是在作一样事情上的不一样实现,因此意味着他俩对外提供的能力大部分是相同的,这部分不赘述,咱们看不一样的地方:

Koa 内部也有几个神行太保,能力较大,首先 new Koa() 出来的服务器运行实例,它像青蛙同样,张大嘴吞食全部的请求,经过它能够把服务真正跑起来,跟 Express 同样,这个就跳过不提了,重点是它的 context,也就是 ctx,这货上面有不少引用,最核心的是 request 和 response,这俩能够对应到 Express 两个对立的 req 和 res,在 Koa 里面,把它俩都集中到 ctx 里面进行管理,分别经过 ctx.request 和 ctx.reponse 进行直接访问,原来 Express 两个独立对象作的事情,如今一个 ctx 就够了,上下文对象都在他手中,想要联系谁就能联系谁。
其次是它的中间件机制,Koa 真正的魅力所在,先看段代码:

const Koa = require('koa')
const app = new Koa()
const indent = (n) => new Array(n).join(' ')
const mid1 = () => async (ctx, next) => {
  ctx.body = `<h3>请求 => 第一层中间件</h3>`
  await next()
  ctx.body += `<h3>响应 <= 第一层中间件</h3>`
}
const mid2 = () => async (ctx, next) => {
  ctx.body += `<h3>${indent(4)}请求 => 第二层中间件</h3>`
  await next()
  ctx.body += `<h3>${indent(4)}响应 <= 第二层中间件</h3>`
}
app.use(mid1())
app.use(mid2())
app.use(async (ctx, next) => {
  ctx.body += `<p style="color: #f60">${indent(12)}=> Koa 核心 处理业务 <=</p>`
})
app.listen(2333)

你们能够把这 22 行代码跑起来,浏览器里访问 localhost:2333 就能看到代码的执行路径,一个 HTTP 请求,从进入到流出,是两次穿透,每个中间件都被穿透两次,这个按照次序的正向进入和反向穿透并非必选项,而是 Koa 轻松具有的能力,一样的能力,在 Express 里面实现反而很费劲。

Koa2 源码简要分析

想要了解上面提到的能力,就要看下 Koa 核心的代码:
一样是 app.use(middlewares()),在 koa/application.js 里面,每个中间件一样被压入到一个数组中:

use(fn) {
  this.middleware.push(fn)
}

在服务器启动的时候,创建监听,同时注册回调函数:

listen(...args) {
  server = http.createServer(this.callback()).listen(...args)
}

回调函数里面,返回了 (req, res) 给 Node.js 用来接收请求,在它内部,首先基于 req, res 建立出来 ctx,就是那个同时能管理 request 和 response 的家伙,重点是上面压到数组里面的 middlewares 被 compose 处理后,就扔给了 handleRequest:

callback() {
  const fn = compose(this.middleware)
  return handleRequest = (req, res) => {
    const ctx = this.createContext(req, res)
  
    return this.handleRequest(ctx, fn)
  }
}

compose 就是 koa-compose,简单理解为经过它,以递归的方式实现了 Promise 的链式执行,由于咱们都知道, async function 本质上会返回一个 Promise,这里 compose 跳过不说了,继续去看 handleRequest:

handleRequest(ctx, fnMiddleware) {
  return fnMiddleware(ctx).then(respond(ctx))
}

实在是简洁的不像实力派,请求进来后,会把能够递归调用的中间件数组都执行一遍,每一个中间件都能拿到 ctx,同时,由于 async function 的语法特性,能够中间件中,把执行权交给后面的中间件,这样逐层逐层交出去,最后再逐层逐层执行回来,就达到了请求沿着一条路进入,响应沿着一样的一条路反向返回的效果。
借用官方文档的一张图来表达这个过程:

图片描述

我知道这张图还不够,再祭出官方的第二张图,著名的洋葱模型:

图片描述

Koa2 要学习什么

从上面的对比,咱们其实就发现了 Koa2 独具魅力的地方,这些魅力一方面跟框架设计理念有关,一方面跟语言特性有关,语言特性,无外乎下面几个:

  • 箭头函数
  • Promise 规范
  • 迭代器生成器函数执行原理
  • 异步函数 Async Function
  • 以及 Koa2 的应用上下文 ctx 的经常使用 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 的冲击和影响, 也要升级到 async function,保持对语法的跟进,而这些都是 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 应用服务框架而已,它全部与操做系统之家的沟通能力,都创建在 Node.js 整个的通讯服务模型的基础之上,Nodejs 提供了 filesystem 也就是 fs 这个模块,模块中提供了文件读写的接口,好比 readFile 这个异步的接口,它就是一个典型的异步 IO 接口,反之 readFileSync 就是一个阻塞的同步 IO 接口,以这个来类推,咱们站在上层的 web 服务这个层面,就很容易理解 Node.js 的异步非阻塞模型,异步 IO 能力。

那么 Node.js 的异步能力又是创建在 Libuv 这一层的几个阶段上的,什么?还有阶段?

是的,Node.js 的底层除了解释和执行 JS 代码的 Chrome V8 虚拟机,还有一大趴儿就是 Libuv,它跟操做系统交互,封装了不一样平台的诸多接口,至关于抹平了操做系统的异步差别带来的兼容性,让 Node.js 对外提供一致的同异步 API,而 Libuv 的几个阶段,即是对于单线程的 JS 最有利的辅助实现,全部的异步均可以看作是任务,任务是耗时的,libuv 把这些任务分红不一样类型,分到不一样阶段,有他们各自的执行规律和执行优先级。

你们能够先预测下下面这段代码的执行结果:

const EventEmitter = require('events')
class EE extends EventEmitter {}
const yy = new EE()
yy.on('event', () => console.log('粗大事啦'))
setTimeout(() => console.log('0 毫秒后到期的定时器回调'), 0)
setTimeout(() => console.log('100 毫秒后到期的定时器回调'), 100)
setImmediate(() => console.log('immediate 当即回调'))
process.nextTick(() => console.log('process.nextTick 的回调'))
Promise.resolve().then(() => {
  yy.emit('event')
  process.nextTick(() => console.log('process.nextTick 的回调'))
  console.log('promise 第一次回调')
})
.then(() => console.log('promise 第二次回调'))

你会发现你踏入了一个 【美好】 的世界,这就是咱们经过了解 Koa 之后,若是想要继续往下学习,须要掌握的知识,这块知识才是真正的干货,一言半语的确说不清楚,咱们保留思路往下走。

Koa2 的三方库生态如何

在 Koa1 时代和 Koa2 刚出的时候,的确它的三方库很少,须要本身动手包装,甚至还有 koa-convert 专门干这个活儿,把 1 代 koa 中间件转成能够兼容 2 代 koa 能够兼容的形式。

可是时至今日,Koa2 的生态已经至关完善了,尤为在 2018 年随着更多开发者切入到 Koa2 中,将会有大批量的业界优秀模块库进入到 Koa2 的大池子中,你们会发现可选择的愈来愈多,因此他的生态没问题。

跟前端如何结合

到这里,本文接近尾声了,我也感受意犹未尽,可是再写下去怕是成飞流直下三千尺了,我想用一句话回答这个问题:
小而美是每个工程师最终会选择自我修养,Koa2 是小而美的,能与它结合的必然也是小而美的,那么在 2018 年,就非 Parcel 莫属,小而美绝配,关于 Parcel 如何 AntDesign/React/Bootstrap 等这些前端框架库组合使用,能够关注 Koa2解读+数据抓取+实战电影网站 了解更多姿式。

回到本文的标题:Koa2 还有多久取代 Express?我想彻底替代是不可能的,可是新项目使用 Koa2(以及基于它封装的框架)将会在数量上碾压 Express,时间呢,2018 - 2019 两年足矣,那么 2018 年起,但求不落后,加油!

封面图来自 codetrick
相关文章
相关标签/搜索