资讯专栏INFORMATION COLUMN

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

yagami / 2657人阅读

摘要:改造成服务类接下来在这个基础上改造一下,把封装成一个对象。新建,测试代码如下封装上下文对象为了实现类似那种这样的方式,先来新建个文件,,。


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

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

那么如果 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://www.ucloud.cn/yun/53131.html

相关文章

  • 漫谈Nuclear Web组件化入门篇

    摘要:目前来看,团队内部前端项目已全面实施组件化开发。层叠样式保佑不要污染别的在前端,一般一个组件必须要有骨架和装饰的以及逻辑。事件绑定条件判断秒后改变,会自动变更。循环姓名年龄增加修改移除的变更也能监听到,能够自动触发的变更。 目前来看,团队内部前端项目已全面实施组件化开发。组件化的好处太多,如:按需加载、可复用、易维护、可扩展、少挖坑、不改组件代码直接切成服务器端渲染(如Nuclear组...

    VPointer 评论0 收藏0
  • Web框架常用架构模式(JavaScript语言)

    摘要:只能在不同的时候选用不同的假设和不同的理论来解释问题,许来西的文章讲到科学一定程度上通过放弃一贯性换取了实用性,放弃自洽性换取了它洽性。然而遗憾的是本身只提供了模块和洋葱模型的最小封装。 在写干货之前,我想先探(qiang)讨(diao)两个问题,模式的局限性?模式有什么用? 最近看到一篇文章对我启发很大,许来西在知乎的回答《哲学和科学有什么关联?》,全篇较长,这里摘录我要引出的一点:...

    loostudy 评论0 收藏0
  • 你不能不知道Koa实现原理

    摘要:前言什么这是一篇源码解读文章那一定很枯燥不看。通过利用函数,帮你丢弃回调函数,并有力地增强错误处理。并没有捆绑任何中间件,而是提供了一套优雅的方法,帮助您快速而愉快地编写服务端应用程序。 showImg(https://segmentfault.com/img/bVNQYf?w=1020&h=790); 前言 什么?这是一篇源码解读文章 ? 那一定很枯燥!不看。 我把 Koa 的核心实...

    LinkedME2016 评论0 收藏0
  • koa2开发微信公众号: 不定期推送最新币圈消息

    摘要:背景比特币说好的分叉最后却分叉不成,如今算力又不够,于是比特现金想篡位没一个星期就涨了快倍,错过这趟快车甚是后悔,于是打算写一个可不定期推送最新消息的微信公众号。既然是利用微信这个平台载体,当然要熟悉微信的,遂封装了一下。 背景:比特币说好的segwit2x分叉最后却分叉不成,如今算力又不够,于是比特现金想篡位? 没一个星期就涨了快10倍,错过这趟快车甚是后悔,于是打算写一个可不定期推...

    xi4oh4o 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<