程式碼改變世界 | 如何封裝一個簡單的 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
微信訂閱號“妙味前端”,為您帶來優質前端技術乾貨;