下面給你們帶來:封裝一個簡單的 Koa前端
Koa 是基於 Node.js 平臺的下一代 web 開發框架node
Koa 是一個新的 web 框架,能夠快速而愉快地編寫服務端應用程序,本文將跟你們一塊兒學習:封裝一個簡單的 Koaweb
一個簡單的 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,便可訪問。微信
改形成服務類app
接下來在這個基礎上改造一下,把 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!') })
封裝上下文對象async
爲了實現相似 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
微信訂閱號「妙味前端」,爲您帶來優質前端技術乾貨;