代码改变世界 | 如何封装一个简单的 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:
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的更多相关文章
- 利用代码改变世界 #AzureDev
毫无疑问,开发人员是 //build/ 2013 的主角.开发人员是我们这个行业的心脏和灵魂,我们很感谢他们所做的一切.在 Satya Nadella 走上讲台发表第 2 天的主题演讲之前,我们播放了 ...
- Directx11学习笔记【四】 封装一个简单的Dx11DemoBase
根据前面两个笔记的内容,我们来封装一个简单的基类,方便以后的使用. 代码和前面类似,没有什么新的内容,直接看代码吧(由于代码上次都注释了,这次代码就没怎么写注释o(╯□╰)o) Dx11DemoBas ...
- 网络游戏开发-服务器(01)Asp.Net Core中的websocket,并封装一个简单的中间件
先拉开MSDN的文档,大致读一遍 (https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/websockets) WebSocket 是一 ...
- python+selenium之自定义封装一个简单的Log类
python+selenium之自定义封装一个简单的Log类 一. 问题分析: 我们需要封装一个简单的日志类,主要有以下内容: 1. 生成的日志文件格式是 年月日时分秒.log 2. 生成的xxx.l ...
- Python之自定义封装一个简单的Log类
参考:http://www.jb51.net/article/42626.htm 参考:http://blog.csdn.net/u011541946/article/details/70198676 ...
- Python+Selenium中级篇之8-Python自定义封装一个简单的Log类《转载》
Python+Selenium中级篇之8-Python自定义封装一个简单的Log类: https://blog.csdn.net/u011541946/article/details/70198676
- C 封装一个简单二叉树基库
引文 今天分享一个喜欢佩服的伟人,应该算人类文明极大突破者.收藏过一张纸币类型如下 那我们继续科普一段关于他的简介 '高斯有些孤傲,但令人惊奇的是,他春风得意地度过了中产阶级的一生,而 没有遭受到冷 ...
- 如何用C++封装一个简单的数据流操作类(附源码),从而用于网络上的数据传输和解析?
历史溯源 由于历史原因,我们目前看到的大部分的网络协议都是基于ASCII码这种纯文本方式,也就是基于字符串的命令行方式,比如HTTP.FTP.POP3.SMTP.Telnet等.早期操作系统UNIX( ...
- 封装一个简单好用的打印Log的工具类And快速开发系列 10个常用工具类
快速开发系列 10个常用工具类 http://blog.csdn.net/lmj623565791/article/details/38965311 ------------------------- ...
随机推荐
- canvas学习之柱状图
项目地址:http://pan.baidu.com/s/1nvhWrwP 因为最近项目中使用到了图表,而且个人一直希望研究canvas,所以最近几天花时间对canvas好好研究了一下,并写了一个dem ...
- Confluence 6 设置其他页面为你空间的主页
在任何时候,如果你希望某一个页面称为你空间的主页,你可以非常容易的从 编辑空间细节(Edit Space Details)标签页中进行修改. 希望编辑空间的细节: 进入空间后,然后从边栏的底部选择 空 ...
- Vue音乐项目笔记(三)
1. 音乐播放前进后退的实现 https://blog.csdn.net/weixin_40814356/article/details/80379606 2. 音乐进度条实现(单独一个组件) h ...
- BGP华为、思科选路规则
选路规则 华为BGP选路规则 思科BGP选路规则 第0条 下一跳是否可达,如果不可达则不参与选路 BGP 向IBGP对等体发布import引入的IGP路由时, 将下一跳属性改为自身的接口地址,而非IG ...
- J - Jesus Is Here HDU - 5459 (递推)
大意: 定义$f_1="c",f_2="ff",f_n=f_{n-2}+f_{n-1}$, 求所有"cff"的间距和. 记录c的个数, 总长 ...
- arp欺骗图解
ARP协议:地址转换协议,工作在OSI模型的数据链路层,在以太网中,网络设备之间互相通信是用MAC地址而不是IP地址,ARP协议就是用来把IP地址转换为MAC地址的. 防止ARP攻击的方法: 1.使用 ...
- ZCRM_DAY_IN_WEEK
FUNCTION zcrm_day_in_week. *"------------------------------------------------------------------ ...
- NOIP2012国王游戏(60分题解)
题目描述 恰逢 H国国庆,国王邀请n 位大臣来玩一个有奖游戏.首先,他让每个大臣在左.右手上面分别写下一个整数,国王自己也在左.右手上各写一个整数.然后,让这 n 位大臣排成一排,国王站在队伍的最前面 ...
- volatile原理解析
Java并发编程:volatile关键字解析 volatile 有序性.可见性 volatile可以保证一定程度上有序性,即volatile前面的代码先于后面的代码先执行. 但是前.后代码,各自里面的 ...
- Qt Widgets——菜单和菜单栏
主窗口MainWindow需要菜单栏QMenuBar及菜单QMenu来组成自身,一般应用程序的所有功能都能在菜单中找到.接下来就来说说它们. QMenu 它添加了很多动作QAction,并用自身组成了 ...