Koa(1)之——koa入门

4,053 阅读6分钟

  koa是基于Node.js的一个新的web框架,它的特点是轻量、健壮、富有表现力。它是Express的下一代基于Node.js的web 框架,koa2完全使用Promise配合async来实现异步。

特点

  1. 轻量、无捆绑(koa不再内核方法中绑定任何中间件,它仅仅提供一个轻量的函数库,几乎所有的功能都需要引用第三方中间件来实现)
  2. 中间件架构(函数式编程的一种实践,开发者可以根据业务和项目需要使用已有的第三方中间件或者定制中间件)
  3. 优雅的API设计
  4. 增强的错误处理(async语法对于错误处理的增强)

context对象

  Koa将Node.js的Request请求和Response响应对象封装到Context对象中,所以也可以把Context对象成为一次对话的上下文,通过加工Context对象,就可以控制返回给用户的内容。像Express中的Req和Res都封装到了Context中。Context中还内置了一些常用的属性:

  1. req Node的request
  2. res Node的response
  3. request Koa的request
  4. response Koa的response
  5. state 推荐的命名空间,用于中间件传递消息和前端视图
  6. app 应用程序引用

Koa的中间件机制

  Koa的应用程序其实就是一个包含一组中间件函数的对象,通过app.use函数来加载中间件,这个函数有两个参数,context指的是上下文环境对象,封装了一些属性;next用于把中间件的执行权交给下游的中间件,在当前中间件中位于next()之后的代码会暂停执行,直到最后一个中间件执行完毕,再自下而上依次执行每个中间件中next值周的代码,类似于栈的先进后出。这种模型被称作“洋葱圈模型”。

  洋葱圈的最外层是最上层的中间件,由上自下执行每个中间件中next()函数之前的代码,之后由下自上执行每个中间件中next()函数之后的代码。Koa的大部分功能都是通过中间件实现的。

代码示例

const koa = require('koa')
const app = new koa()
app.use(async function (ctx, next) { //中间件1,位于最上层
  console.log('one start') //(1)
  ctx.body = 'Hello Koa' //(2)
  await next()
  ctx.body = ctx.body + '!!!'//(9)
  console.log('one end') //(10)
})
app.use(async function (ctx, next) { //中间件2,位于中间
  console.log('two start') //(3)
  ctx.type = 'text/html;charset=utf-8' //(4)
  await next()
  console.log('two end') //(8)
})
app.use(async function (ctx, next) { //中间件3,位于最下层
  console.log('three start') //(5)
  ctx.body = ctx.body + ', I am zhunny' //(6)
  await next()
  console.log('three end') //(7)
})
app.listen(3000, () => {
  console.log('server is running at http://localhost:3000')
})

浏览器中显示如下:


node控制台打印结果如下:

错误处理

  某个中间件出错,可以在它上一级的错误处理中间件中捕获,再一层层向上捕获,从全局app应用层最后到Node层。Koa的错误处理是向上抛的。

const koa = require('koa')
const app = new koa()

//响应输出中间件
app.use(async function (ctx, next) {
  await next()
  //获取响应头,印证执行顺序
  const rt = ctx.response.get('X-Response-Time')
  console.log(`输出计时:${ctx.method} ${ctx.url} - ${rt}`)
})

app.use(async function (ctx, next) {
  const start = Date.now()
  console.log('开始计时')
  await next()
  const ms = Date.now() - start
  ctx.set('X-Response-Time', `${ms}ms`)
  console.log('计时结束')
})

//中间件错误捕获
app.use(async (ctx, next) => {
  try {
    await next()
  } catch (error) {
    ctx.status = error.statusCode || error.status || 500
    ctx.body = error.message

    //触发应用层级的错误事件
    ctx.app.emit('error', error, ctx)
    //中间件出错,可以上抛到中间件错误捕获->全局->Node 
    console.log('中间件捕捉', error.message)
  }
})

app.use(async function (ctx, next) {
  console.log('响应用户请求')
  //这里设置一个未定义的函数
  sleep(300)
  ctx.status = 200
  ctx.type = 'html'
  ctx.body = '<h1>Hello Koa</h1>'
})

//全局的错误捕获
app.on('error', err => {
  console.error('app全局错误:', err.message)

  //继续上抛到Node,此时会中止服务
  throw err
})

app.listen(3000)

node控制台打印结果如下:

路由

  路由具有引导、匹配之意。路由匹配是根据URL的变更重新渲染页面布局和内容的过程。

前端路由和后端路由

  早期,前后端没有分离时,由后端来实现路由。用户将每个页面的静态资源全部都放到后端服务器上,当用户进行页面切换时,由浏览器向服务器发送不同的URL请求,经服务器解析后向浏览器返回对应页面的静态资源和数据,再由浏览器渲染成新的页面。后端路由的弊端是每次切换页面都会刷新页面,给用户造成一种卡顿的感觉,用户体验很差。前后端不分离,路由是后端开发人员来做的,整个业务偏重后端,后端开发任务繁重,且前后端不解耦,使得服务器压力大,开发效率也低。

  前后端分离时代的到来,使得前后端可以并行开发,开发效率,项目性能大大提升。前端服务器负责控制页面引用&跳转&路由,前端页面异步调用后端的接口,后端/应用服务器使用tomcat(把tomcat想象成一个数据提供者),加快整体响应速度。前端路由不再是每次都刷新页面,而是在需要的时候才加载相应页面的内容。不同路由切换对应的仅仅是一个文档树的切换,页面所需的数据才会向后端服务器发起请求。前端路由的主要场景是SPA单页面应用,Vue、React等流行的前端框架都提供了路由切换。

koa路由

自定义路由

  如果不采用路由组件应该如何根据不同的url和方法做不同的响应?如下自定义一个404页面:

const koa = require('koa')
const app = new koa()

app.use(async (ctx, next) => {
  if (ctx.url === '/' && ctx.method === 'GET') {
    ctx.body = 'Page Not Found'
    ctx.status = 404
  } else {
    ctx.body = 'Defalut Page'
    ctx.status = 200
  }
  await next()
})

  上述这种方式将路由处理和输出响应都放在了一个中间件函数中,而实际的项目中会存在很多的路由,如果按照这样的方式处理,会严重影响到代码的可读性和可维护性。我们通常使用路由组件来定义路由。

koa-router

  koa-router具有丰富的API,可以实现命名路由、命名参数、多路由中间件、嵌套路由等多种功能。上述的代码可以用koa-router来改写:

const koa = require('koa')
const app = new koa()
const Router = require('koa-router')
const router = new Router()

router.get('/', (ctx, next) => {
   ctx.body = 'Page Not Found!!!'
   ctx.status = 404
})

app.use(router.routes())

app.listen(3000)

  在实际项目中,根据不同的职能将路由分为不同的模块,在入口文件中统一调用这些路由模块。例如现在有一个users模块,负责用户信息的增删改查,一个index模块,负责默认页面路由。


index.js的内容如下

const Router = require('koa-router')
const router = new Router()

router.get('/', ctx => {
  ctx.body = 'index'
})

module.exports = router

users.js的内容如下:

const Router = require('koa-router')
const router = new Router({ prefix: '/users' })

router.get('/', ctx => {
  ctx.body = 'user'
})

module.exports = router

入口文件中使用这些路由中间件:

const index = require('./routes/index')
const users = require('./routes/users')
app.use(index.routes())
app.use(users.routes())

浏览器中显示如下: