代碼改變世界 | 如何封裝一個簡單的 Koa

 下面給你們帶來:封裝一個簡單的 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

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

相關文章
相關標籤/搜索