下面给大家带来:封装一个简单的 Koa

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

Koa 是一个新的 web 框架,可以快速而愉快地编写服务端应用程序,本文将跟大家一起学习:封装一个简单的 Koa

一个简单的 http 服务

使用 node 提供的 http 模块,可以很容易的实现一个基本的 http 服务器,新建一个 application.js 文件,内容如下:

 const http = require('http')

    const server = http.createServer((req, res) => {

      res.end('Hello, Fq!')

    })

    server.listen(8080, () => {

      console.info('Server is running at 8080')

    })

  

之后通过 node 来启动这个脚本,打开浏览器 输入地址 localhost:8080,即可访问。

改造成服务类

接下来在这个基础上改造一下,把 server 封装成一个对象。

  const http = require('http')

    class Application () {

      constructor () {}

      use (cb) {

        this.callback = cb

      }

      listen (...args) {

        const server = http.createServer((req, res) => {

          this.callback(req, res)

        })

        server.listen(...args)

      }

    }

    module.exports = Application

  

新建 server.js ,测试代码如下:

const Koa = require('./application.js')

    const app = new Koa()

    app.use((req, res) => {

      res.end('Hello, Fq!')

    })

    app.listen(8080, () => {

      console.log('Server started!')

    })

  

封装上下文对象

为了实现类似 Koa 那种 ctx.xxx 这样的方式,先来新建3个文件:request.js,response.js,context.js 。

    // request.js  以 url 为例:

    const request = {

      get url () {

        return this.req.url

      }

    }

    module.exports = request

 

 

    // response.js

    const reponse = {

      get body () {

        return this._body

      },

      set body (val) {

        this._body = val

      }

    }

    module.exports = reponse

  

  // context.js

    const context = {

      get url () {

        return this.request.url

      },

      get body () {

      return this.response.body

      },

      set body (val) {

        this.response.body = val

      }

    }

    module.exports = context

  

整合上下文对象到服务类

可能看到上面3个对象,会有点迷糊的感觉,下面就把这3个对象添加到 Application 类中:

    const http = require('http')

    const request = require('./require.js')

    const response = require('./response.js')

    const context = require('./context.js')

    class Application {

      constructor () {

      // 先把这3个属性添加到构造函数中

        this.context = context

        this.request = request

        this.response = response

      }

      use (cb) {

        this.callback = cb

      }

      createCtx (req, res) {

      // 新建 ctx 对象,并且继承于 context

        const ctx = Object.create(this.context)

      // 像 ctx 对象添加两个属性 request  response

        ctx.request = Object.create(this.request)

        ctx.response = Object.create(this.response)

        // 像 ctx 添加 req res 属性,同时挂载到 response request 对象上

        // req res 为 nodejs http 模块的 原生对象

        ctx.req = ctx.request.req = req

        ctx.res = ctx.response.res = res

        return ctx

      }

      listen (...args) {

      // 这里改造成 异步形式

        const server = http.createServer(async (req, res) => {

          const ctx = this.createCtx(req, res)

          await this.callback(ctx)

          ctx.res.end(ctx.body)

        })

        server.listen(...args)

      }

    }

    module.exports = Application

  

修改 server.js 文件,再次测试:

const Koa = require('./application.js')

const app = new Koa()

app.use(async (ctx) => {

  ctx.body = ctx.url

})

app.listen(8080, () => {

  console.log('Server started!')

})

  

串联中间件

到此为止,咱们写的 Koa 只能使用一个中间件,而且还不涉及到异步,下面咱们就一起来看看 Koa 中最核心的 compose 函数,是如何把各个中间件串联起来的。

为了更容易的理解,先来写一个同步版本的,依次执行 fn1, fn2:

 function compose (middlewares) {

      return (x) => {

        let ret = middlewares[0](x)

     for (let i=1; i<middlewares.length; i++) {

          ret = middlewares[i](ret)

        }

     return ret

      }

    }

   const fn = compose([fn1, fn2])

   console.log(fn(2))  // 8

  

上面代码可以直接在浏览器中测试结果。

那么如果 fn1 fn2 中如果有异步操作,应该如何处理呢,实际上只需要使用 Promise 改造一下 compose 的逻辑即可。

首先实现一个测试用休眠函数:

 const sleep = (duratioin = 2000) => new Promise((resolve) => {

      setTimeout(resolve, duratioin)

    })

  

其次准备3个测试用异步函数,最终效果是实现一个洋葱圈模型:

const fn1 = async (next) => {

      console.log('fn1 start 休眠2秒')

      await sleep()

      await next()

      console.log('fn1 over')

    }

    const fn2 = async (next) => {

      console.log('fn2 start 休眠3秒')

      await sleep(3000)

      await next()

      console.log('fn2 duration....')

      await sleep(1000)

      console.log('fn2 over')

    }

    const fn3= async (next) => {

      console.log('fn3 start')

      await sleep()

      console.log('fn3 over')

    }

  

执行的顺序为 fn1  >  fn2 > fn3 > fn2 > fn1

最后就是主角 componse

function compose (middlewares) {

      return (context) => {

        return dispatch(0)

        function dispatch (i) {

          const fn = middlewares[i]

          if (!fn) return Promise.resolve()

          return Promise.resolve(fn(function next () {

                  // await 的本质就是 一个返回 Promise 

                     对象的函数

                  // 所以这里一定要 return

            return dispatch(i+1)

          }))

        }

      }

    }

  

测试用例:

const fn = compose([fn1, fn2, fn3])

fn()

  

效果如下图:

整合compose到Server

废话不说,直接上代码:

 class Application {

      constructor () {

        this.context = context

        this.request = request

        this.response = response

        this.middlewares = []

      }

      use (middleware) {

        this.middlewares.push(middleware)

        return this

      }

      createCtx (req, res) {

        const ctx = Object.create(this.context)

        ctx.request = Object.create(this.request)

        ctx.response = Object.create(this.response)

        ctx.req = ctx.request.req = req

        ctx.res = ctx.response.res = res

        return ctx

      }

      compose (middlewares) {

        return ctx => {

          return dispatch(0)

          function dispatch (index) {

            const fn = middlewares[index++]

            if (!fn || typeof fn !== 'function') {

        return Promise.resolve()

      }

            return Promise.resolve(fn(ctx, next))

            function next () {

              return dispatch(index)

            }

          }

        }

      }

      listen (...rest) {

        const server = http.createServer(async (req, res) => {

          const ctx = this.createCtx(req, res)

          const fn = this.compose(this.middlewares)

          await fn(ctx) 

          ctx.res.end(ctx.body)

        })

        server.listen(...rest)

      }

    }

    module.exports = Application

  

下面可以测试一下了~

  const Koa = require('./application.js')

    const app = new Koa()

    const sleep = (time) => new Promise((resolve, reject) => {

      setTimeout(resolve, time || 2000)

    })

    app.use(async (ctx, next) => {

      ctx.body = 'Hello'

      await sleep()

      await next()

      ctx.body += 'q!'

    })

    app.use(async (ctx, next) => {

      ctx.body += ', My name is'

      await sleep()

      await next()

    })

    app.use(async (ctx, next) => {

      ctx.body += ' F'

    })

    app.listen(8080, () => {

      console.log('Server started!')

    })

  

到此为止,一个简单的 Koa 就实现完毕了,是不是 so easy ?

——以上是笔者归纳总结,如有误之处,欢迎指出。

原创: 付强 想要关注更多作者文章可关注:微信订阅号ID:Miaovclass

微信订阅号“妙味前端”,为您带来优质前端技术干货;

代码改变世界 | 如何封装一个简单的 Koa的更多相关文章

  1. 利用代码改变世界 #AzureDev

    毫无疑问,开发人员是 //build/ 2013 的主角.开发人员是我们这个行业的心脏和灵魂,我们很感谢他们所做的一切.在 Satya Nadella 走上讲台发表第 2 天的主题演讲之前,我们播放了 ...

  2. Directx11学习笔记【四】 封装一个简单的Dx11DemoBase

    根据前面两个笔记的内容,我们来封装一个简单的基类,方便以后的使用. 代码和前面类似,没有什么新的内容,直接看代码吧(由于代码上次都注释了,这次代码就没怎么写注释o(╯□╰)o) Dx11DemoBas ...

  3. 网络游戏开发-服务器(01)Asp.Net Core中的websocket,并封装一个简单的中间件

    先拉开MSDN的文档,大致读一遍 (https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/websockets) WebSocket 是一 ...

  4. python+selenium之自定义封装一个简单的Log类

    python+selenium之自定义封装一个简单的Log类 一. 问题分析: 我们需要封装一个简单的日志类,主要有以下内容: 1. 生成的日志文件格式是 年月日时分秒.log 2. 生成的xxx.l ...

  5. Python之自定义封装一个简单的Log类

    参考:http://www.jb51.net/article/42626.htm 参考:http://blog.csdn.net/u011541946/article/details/70198676 ...

  6. Python+Selenium中级篇之8-Python自定义封装一个简单的Log类《转载》

    Python+Selenium中级篇之8-Python自定义封装一个简单的Log类: https://blog.csdn.net/u011541946/article/details/70198676

  7. C 封装一个简单二叉树基库

    引文 今天分享一个喜欢佩服的伟人,应该算人类文明极大突破者.收藏过一张纸币类型如下 那我们继续科普一段关于他的简介 '高斯有些孤傲,但令人惊奇的是,他春风得意地度过了中产阶级的一生,而  没有遭受到冷 ...

  8. 如何用C++封装一个简单的数据流操作类(附源码),从而用于网络上的数据传输和解析?

    历史溯源 由于历史原因,我们目前看到的大部分的网络协议都是基于ASCII码这种纯文本方式,也就是基于字符串的命令行方式,比如HTTP.FTP.POP3.SMTP.Telnet等.早期操作系统UNIX( ...

  9. 封装一个简单好用的打印Log的工具类And快速开发系列 10个常用工具类

    快速开发系列 10个常用工具类 http://blog.csdn.net/lmj623565791/article/details/38965311 ------------------------- ...

随机推荐

  1. 无线网络覆盖-java中,用Math.sqrt()时,必须要注意小数问题

    时间限制:3000 ms  |  内存限制:65535 KB 难度:3 描述 我们的乐乐同学对于网络可算得上是情有独钟,他有一个计划,那就是用无线网覆盖郑州大学. 现在学校给了他一个机会,因此他要购买 ...

  2. PHP中工厂模式与策略模式区别

    策略模式需要自己动手去做,工厂模式是都准备好了你需要选择 工厂模式:有一天你决定去吃披萨,一看菜单,哦,种类很多呀,你就点了个培根披萨,过了二十分钟,你的披萨就来了就可以吃到了.但这个披萨是怎么做的, ...

  3. php自动填充

    1.str_pad() 函数把字符串填充为新的长度. 2.str_pad(string,length,pad_string,pad_type) 参数 描述 string 必需.规定要填充的字符串. l ...

  4. Spring + Mybatis项目实现数据库读写分离

    主要思路:通过实现AbstractRoutingDataSource类来动态管理数据源,利用面向切面思维,每一次进入service方法前,选择数据源. 1.首先pom.xml中添加aspect依赖 & ...

  5. logmnr使用

    logminer 工具的使用 Oracle LogMiner 是Oracle公司从产品8i以后提供的一个实际非常有用的分析工具,使用该工具可以轻松获得Oracle 重作日志文件(归档日志文件)中的具体 ...

  6. Git的安装和创建版本库

    1.Git是分布式版本控制系统 2.安装Git 下载Git后,按照默认设置即可实现安装,安装完毕后点击git目录下的Git Bash 输入以下命令符: git config --global user ...

  7. python dpkt 解析 pcap 文件

    dpkt Tutorial #2: Parsing a PCAP File 原文链接:https://jon.oberheide.org/blog/2008/10/15/dpkt-tutorial-2 ...

  8. vue 父组件通过props向子组件传递数据/方法的方式

    参考网址:https://segmentfault.com/a/1190000010507616 下面栗子中, callback是传递父组件的方法, mutationName是传递父组件的数据, Ap ...

  9. adb(Android Debug Bridge)安装使用教程

    一.说明 adb的db是debug bridge而不是和gdb一样指debug,这意思是说adb不能像gdb那样能一步步调试代码,但可以启到一些类似调试的功能. 下面就针对这些功能进行介绍,本文根据官 ...

  10. [转]java nio解决半包 粘包问题

    java nio解决半包 粘包问题 NIO socket是非阻塞的通讯模式,与IO阻塞式的通讯不同点在于NIO的数据要通过channel放到一个缓存池ByteBuffer中,然后再从这个缓存池中读出数 ...