代碼改變世界 | 如何封裝一個簡單的 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 封裝成一個對象。node

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 ,測試代碼以下:web

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

app.use(async (ctx) => {
ctx.body = ctx.url
})框架

app.listen(8080, () => {
console.log('Server started!')
})
串聯中間件
到此爲止,我們寫的 Koa 只能使用一箇中間件,並且還不涉及到異步,下面我們就一塊兒來看看 Koa 中最核心的 compose 函數,是如何把各個中間件串聯起來的。異步

爲了更容易的理解,先來寫一個同步版本的,依次執行 fn1, fn2:async

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

微信訂閱號「妙味前端」,爲您帶來優質前端技術乾貨;

相關文章
相關標籤/搜索