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

下面给大家带来:封装一个简单的 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:


const fn1 = x => Math.pow(x, 2)
const fn2 = x => 2 * x

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

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

原文地址:https://segmentfault.com/a/1190000016816127

原文地址:https://www.cnblogs.com/lalalagq/p/9901568.html

时间: 2024-09-30 19:28:43

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

挨踢部落故事汇(5):扩展新IT领域,用代码改变世界

思想驱动未来!"写一个程序,就像是在创造一个东西,我猜每个程序猿都有一颗改变世界的心."--by疯狂学校 本期主人公疯狂学校是个既懂前端又会后台,上的了厅堂下的了厨房的杂食程序员.大学期间他学的是软件工程专业,对于一个基本没怎么接触电脑的他来说,这无疑是一个挑战. 疯狂学校·Java开发 热衷开发,从C#到Java,从前端到后台,无一不通 由于对电脑基础比较差,刚开学时疯狂学校一有空就去机房练打字,这样坚持了两个月,感觉才慢慢追上了同学的水平.在这个过程中他感觉到从量变到质变,感受到自

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

根据前面两个笔记的内容,我们来封装一个简单的基类,方便以后的使用. 代码和前面类似,没有什么新的内容,直接看代码吧(由于代码上次都注释了,这次代码就没怎么写注释o(╯□╰)o) Dx11DemoBase.h Dx11DemoBase.h #pragma once #include <d3d11.h> #include <D3DX11.h> #include <DxErr.h> class Dx11DemoBase { public: Dx11DemoBase(); vi

Python+Selenium进阶版(八)- Python自定义封装一个简单的Log类

目标:如何写一个Python日志类,用来输出不同级别的日志信息到本地文件夹下的日志文件里. 练习场景: 我们需要封装一个简单的日志类,主要有以下内容: 1.生成的日志文件格式是 年月日分秒.log 2.生成的XXX.log文件存储在项目根目录下Logs文件夹下 3.这个日志类,支持INFO,ERROR两种日志级别 4.日志里,每行日志输出,时间日期+执行类名称+日志级别+日志描述 解决思路: 1.在根目录下新建一个Logs的文件夹,获取这个Log的相对路径: 2.日志的保存命名,需要系统时间:

封装一个简单的solrserver组件

一个简单的solrserver组件 实现索引更新的异步处理,以及查询接口,日志/线程池/队列监控没有加上. SolrDocment封装 接口: public interface ISolrDocument { public SolrInputDocument convertToInputDocument() throws Exception; public void buildSolrDocument(SolrDocument document) throws Exception; } 实现:

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

引文 今天分享一个喜欢佩服的伟人,应该算人类文明极大突破者.收藏过一张纸币类型如下 那我们继续科普一段关于他的简介 '高斯有些孤傲,但令人惊奇的是,他春风得意地度过了中产阶级的一生,而  没有遭受到冷酷现实的打击:这种打击常无情地加诸于每个脱离现实环境生活的  人.或许高斯讲求实效和追求完美的性格,有助于让他抓住生活中的简单现实.  高斯22岁获博士学位,25岁当选圣彼德堡科学院外籍院士,30岁任哥廷根大学数  学教授兼天文台台长.虽说高斯不喜欢浮华荣耀,但在他成名后的五十年间,这  些东西就像

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

快速开发系列 10个常用工具类 http://blog.csdn.net/lmj623565791/article/details/38965311 ------------------------------------------------------------------------------------------------ 不知众多Android开发者是否在程序开发的工程中也遇到过下面的问题: 0.如何在众多log中快速找到你设置打印的那行log? 1.是否还在不断的切换标签来

代码录播:jQueryMobile 实现一个简单的弹出框效果

今天给大家带来的是 jQueryMobile 实现一个简单的弹出框效果,有兴趣的童鞋可以试试哦~ ^_^ 阅读原文:www.gbtags.com

vue封装一个简单的div框选时间的组件

记录一下我前段时间封装的一个vue组件吧.技术需要积累,有时间我把我之前写的还不错的组件都开源出来.并尝试vue和react 两种方式的组件封装.今天简单写下鼠标框选div选中效果的封装吧. div框选实现 div框选效果,其实没有什么好的方法,就是获取鼠标事件,根据鼠标的位置,动态创建一个跟随鼠标的div.[注:这种方式需要依赖position的定位方式,一般鼠标事件位置是针对全局的,所以鼠标框选的div 位置的position最好父级元素是根元素的定位.不然,鼠标框选区域和被框选区域很难保持

封装一个简单的原生js焦点轮播图插件

轮播图实现的效果为,鼠标移入左右箭头会出现,可以点击切换图片,下面的小圆点会跟随,可以循环播放.本篇文章的主要目的是分享封装插件的思路. 轮播图的我一开始是写成非插件形式实现的效果,后来才改成了封装成插件的形式. 首先要明白轮播图的实现原理和基本布局,大概就是外面有一个容器包裹着(通常是div),容器设置宽高,以及overflow为hidden,超出宽高部分隐藏, 容器里面又包含着另一个容器,包裹着所有的图片,宽为所有图片的总宽度,ul的position为absolute,通过改变ul的left