Koa -- 基于 Node.js 平台的下一代 web 开发框架

Posted 前端工坊

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Koa -- 基于 Node.js 平台的下一代 web 开发框架相关的知识,希望对你有一定的参考价值。

原创 | 京东商城-成都研究院-JSHOP研发部 李乔军


2017年4月26日,来自“火头军”团队的大荒老司机作了一次Koa框架分享,研究院一大半的前端都来捧场,原计划1个小时的分享时间,硬生生讲了1个半小时,楞是没给大家留多少提问时间;

小扁扁评论下,老司机还是太实诚啊,把你的心得体会讲得这么细,大家受益匪浅,100个赞!


Koa -- 基于 Node.js 平台的下一代 web 开发框架Koa -- 基于 Node.js 平台的下一代 web 开发框架


KOA

next generation web framework for node.js

KOA的简介

koa.js 是下一代的node.js框架,由Express团队开发,通过生成器(generators javascript 1.7新引入的,用于解决回调嵌套的方案),减少异步回调,提高代码的可读性和可维护性,同时改进了错误处理(Express的错误处理方式相当糟糕)。

KOA的优点

  • 更优雅、简单、安全的中间件机制

  • 更优雅、简单的异常处理

  • 更优雅、简单的异步编程方式

KOA环境依赖

  • KOA@1 Node环境最低 v0.11.x
    运行server的时候开启 —harmony (或—harmony-generators)来支持ES6的generators

       node --harmony app.js
  • KOA@2 Node环境 node v7.6.x  来支持ES7的async函数

一如既往的hello word

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

app.use(ctx => {
    ctx.body = 'Hello World';
});

app.listen(3000);
// 等价于 http.createServer(app.callback()).listen(3000);

KOA中的上下文 Context

Koa Context 将 node 的 request 和 response 对象封装在一个单独的对象里面,其为编写 web 应用和 API 提供了很多有用的方法。

这些操作在 HTTP 服务器开发中经常使用,因此其被添加在上下文这一层,而不是更高层框架中,因此将迫使中间件需要重新实现这些常用方法。

context 在每个 request 请求中被创建,在中间件中作为接收器(receiver)来引用,或者通过 this 标识符来引用

app.use(function *(){
    this; // is the Context
    this.request; // is a koa Request
    this.response; // is a koa Response
});

许多 context 的访问器和方法为了便于访问和调用,简单的委托给他们的 ctx.request 和 ctx.response 所对应的等价方法, 比如说 ctx.type 和 ctx.length 代理了 response 对象中对应的方法,ctx.path 和 ctx.method 代理了 request 对象中对应的方法。

常用的API

  • ctx.req   Node 的 request 对象。

  • ctx.res   Node 的 response 对象。

  • ctx.request  Koa 的 Request 对象。

  • ctx.response  Koa 的 Response 对象。

  • ctx.app   应用实例引用。

  • ctx.cookies.get(name, [options])

  • ctx.cookies.set(name, value, [options])

signed: 是否要做签名
expires: cookie 有效期时间
path: cookie 的路径,默认为 /
domain: cookie 的域
secure: false 表示 cookie 通过 HTTP 协议发送,true 表示 cookie 通过 HTTPS 发送。
httpOnly: true 表示 cookie 只能通过 HTTP 协议发送

Koa不支持直接调用底层 res 进行响应处理

res.statusCode
res.writeHead()
res.write()
res.end()

KOA@2 对async await语法支持

function getSyncTime() {
    return new Promise((resolve, reject) => {
        try {
            let startTime = new Date().getTime()
            setTimeout(() => {
                let endTime = new Date().getTime()
                let data = endTime - startTime
                resolve( data )
            }, 500)
        } catch ( err ) {
            reject( err )
        }
    })
}

async function getSyncData() {
    let time = await getSyncTime()
    let data = `endTime - startTime = ${time}`
    return data
}

async function getData() {
    let data = await getSyncData()
    console.log( data )
}

getData()

Chrome 提供原生支持async/await


async/await 的特点

  • 可以让异步逻辑用同步写法实现 {:&.rollIn}

  • 最底层的await返回需要是Promise对象

  • 可以通过多层 async function 的同步写法代替传统的callback嵌套

koa@2的中间件写法

app.use(async (next) => {
    var start = new Date;
    await next();
    var ms = new Date - start;
    this.set('X-Response-Time', ms + 'ms');
});

koa@1的中间件写法

app.use(function *(next){
    var start = new Date;
    yield next;
    var ms = new Date - start;
    this.set('X-Response-Time', ms + 'ms');
});

KOA中间件的执行顺序


KOA中间件的开发和使用

generator中间件开发

// generator中间件返回的应该是function* () 函数
function log( ctx ) {
    console.log( ctx.method, ctx.header.host + ctx.url )
}

module.exports = function () {
    return function* ( next ) {

       // 执行中间件的操作
       log( this )

       if ( next ) {
          yield next
       }
    }
}

generator中间件在koa@1中的使用

// generator 中间件在koa v1中可以直接use使用
const koa = require('koa')  // koa v1
const loggerGenerator  = require('./middleware/logger-generator')
const app = koa()

app.use(loggerGenerator())

app.use(function *( ) {
    this.body = 'hello world!'
})

app.listen(3000)
console.log('the server is starting at port 3000')

async中间件开发

function log( ctx ) {
    console.log( ctx.method, ctx.header.host + ctx.url )
}

module.exports = function () {

    return function ( ctx, next ) {

        return new Promise( ( resolve, reject ) => {

            // 执行中间件的操作
            log( ctx )

            resolve()

            return next()

        }).catch(( err ) => {

            return next()
        })
    }
}

async 中间件在koa@2中使用

// async 中间件只能在 koa v2中使用
const Koa = require('koa') // koa v2
const loggerAsync  = require('./middleware/logger-async')
const app = new Koa()

app.use(loggerAsync())

app.use(( ctx ) => {
    ctx.body = 'hello world!'
})

app.listen(3000)
console.log('the server is starting at port 3000')

路由

koa@2 原生路由实现

// 返回页面访问地址
const Koa = require('koa')
const app = new Koa()

app.use( async ( ctx ) => {
  let url = ctx.request.url
  ctx.body = url
})
app.listen(3000)

koa@2 GET请求

在koa中,获取GET请求数据源头是koa中request对象中的query方法或querystring方法,query返回是格式化好的参数对象,querystring返回的是请求字符串,由于ctx对request的API有直接引用的方式,所以获取GET请求数据有两个途径。

  • 1.是从上下文中直接获取
    请求对象ctx.query,返回如 { a:1, b:2 }
    请求字符串 ctx.querystring,返回如 a=1&b=2

  • 2.是从上下文的request对象中获取
    请求对象ctx.request.query,返回如 { a:1, b:2 }
    请求字符串 ctx.request.querystring,返回如 a=1&b=2

koa@2 GET请求

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

app.use( async ( ctx ) => {
    let url = ctx.url
    // 从上下文的request对象中获取
    let request = ctx.request
    let req_query = request.query
    let req_querystring = request.querystring

    // 从上下文中直接获取
    let ctx_query = ctx.query
    let ctx_querystring = ctx.querystring

    ctx.body = {
        url,
        req_query,
        req_querystring,
        ctx_query,
        ctx_querystring
    }
})
app.listen(3000);

koa@2 POST请求

对于POST请求的处理,koa2没有封装获取参数的方法,需要通过解析上下文context中的原生node.js请求对象req,将POST表单数据解析成query string(例如:a=1&b=2&c=3),再将query string 解析成JSON格式(例如:{“a”:”1”, “b”:”2”, “c”:”3”})

// 解析上下文里node原生请求的POST参数
function parsePostData( ctx ) {
    return new Promise((resolve, reject) => {
        try {
            let postdata = "";
            ctx.req.addListener('data', (data) => {
                postdata += data
            })
            ctx.req.addListener("end",function(){
                let parseData = parseQueryStr( postdata )
                resolve( parseData )
            })
        } catch ( err ) {
            reject(err)
        }
    })
}

// 将POST请求参数字符串解析成JSON
function parseQueryStr( queryStr ) {
    let queryData = {}
    let queryStrList = queryStr.split('&')
    console.log( queryStrList )
    for (  let [ index, queryStr ] of queryStrList.entries()  ) {
        let itemList = queryStr.split('=')
        queryData[ itemList[0] ] = decodeURIComponent(itemList[1])
    }
    return queryData
}

KOA的模板引擎

适用于 koa 的模板引擎选择非常多,比如 jade、ejs、nunjucks、xtemplate 等。
怎么选择 ? 主要考虑以下几个因素:

  • 上手难度  xtemplate < nunjucks < ejs < jade  {:&.rollIn}

  • 功能的强大度   nunjucks > xtemplate > jade > ejs

  • 是否支持前后端混用  jade不支持

  • 性能考量  xtemplate 会更优秀些

jade

body
    h1 Jade - node template engine
    #container.col
      if youAreUsingJade
        p You are amazing
      else
        p Get on it! 

ejs

<p><%=: users | first | capitalize %></p>

xtemplate

{{#if(variable===0)}}
  It is true    
{{/if}}

nunjucks

    {% if variable == 0 %}
      It is true
    {% endif %}

koa@2加载模板引擎

const Koa = require('koa')
const views = require('koa-views')
const path = require('path')
const app = new Koa()

// 加载模板引擎
app.use(views(path.join(__dirname, './view'), {
    extension: 'ejs'
}))

app.use( async ( ctx ) => {
    let title = 'hello koa2'
    await ctx.render('index', {
        title,
    })
})

app.listen(3000)

后记

  • ES6/7 带来的变革

自ES6确定和ES7的async/await开始普及,node.js的发展变得更加迅速,可以预见到JavaScript中令人“头疼”的多层嵌套回调(注意是”多层嵌套回调“)将会使用Promise + async/await的方式逐渐替代(不是完全替代,多层嵌套回调也有其特殊的应用场景)。

  • koa2 大势所趋的前景

基于async/await实现中间体系的koa2框架将会是是node.js web开发方向大势所趋的普及框架。基于generator/yield的koa1将会逐渐被koa2替代,毕竟使用co.js来处理generator是一种过渡的方式,虽然有其特定的应用场景,但是用async/await会更加优雅地实现同步写法。

参考

koa官方文档 

koa中文文档

koa框架

koa实战

koa-generator


以上是关于Koa -- 基于 Node.js 平台的下一代 web 开发框架的主要内容,如果未能解决你的问题,请参考以下文章

Koa -- 基于 Node.js 平台的下一代 web 开发框架

koa2 从入门到进阶之路

代码改变世界 | 如何封装一个简单的 Koa

基于 Koa平台Node.js开发的KoaHub.js的模板引擎代码

代码改变世界 | 如何封装一个简单的 Koa

基于 Koa平台Node.js开发的KoaHub.js获取/设置会话功能代码