成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

koa源碼閱讀[1]-koa與koa-compose

vibiu / 3018人閱讀

摘要:接上次挖的坑,對(duì)相關(guān)的源碼進(jìn)行分析第一篇。和同為一批人進(jìn)行開發(fā),與相比,顯得非常的迷你。在接收到一個(gè)請(qǐng)求后,會(huì)拿之前提到的與來(lái)創(chuàng)建本次請(qǐng)求所使用的上下文。以及如果沒(méi)有手動(dòng)指定,會(huì)默認(rèn)指定為。

接上次挖的坑,對(duì)koa2.x相關(guān)的源碼進(jìn)行分析 第一篇。
不得不說(shuō),koa是一個(gè)很輕量、很優(yōu)雅的http框架,尤其是在2.x以后移除了co的引入,使其代碼變得更為清晰。

expresskoa同為一批人進(jìn)行開發(fā),與express相比,koa顯得非常的迷你。
因?yàn)?b>express是一個(gè)大而全的http框架,內(nèi)置了類似router之類的中間件進(jìn)行處理。
而在koa中,則將類似功能的中間件全部摘了出來(lái),早期koa里邊是內(nèi)置了koa-compose的,而現(xiàn)在也是將其分了出來(lái)。
koa只保留一個(gè)簡(jiǎn)單的中間件的整合,http請(qǐng)求的處理,作為一個(gè)功能性的中間件框架來(lái)存在,自身僅有少量的邏輯。
koa-compose則是作為整合中間件最為關(guān)鍵的一個(gè)工具、洋蔥模型的具體實(shí)現(xiàn),所以要將兩者放在一起來(lái)看。

koa基本結(jié)構(gòu)
.
├── application.js
├── request.js
├── response.js
└── context.js

關(guān)于koa整個(gè)框架的實(shí)現(xiàn),也只是簡(jiǎn)單的拆分為了四個(gè)文件。

就象在上一篇筆記中模擬的那樣,創(chuàng)建了一個(gè)對(duì)象用來(lái)注冊(cè)中間件,監(jiān)聽http服務(wù),這個(gè)就是application.js在做的事情。
而框架的意義呢,就是在框架內(nèi),我們要按照框架的規(guī)矩來(lái)做事情,同樣的,框架也會(huì)提供給我們一些更易用的方式來(lái)讓我們完成需求。
針對(duì)http.createServer回調(diào)的兩個(gè)參數(shù)requestresponse進(jìn)行的一次封裝,簡(jiǎn)化一些常用的操作。
例如我們對(duì)Header的一些操作,在原生http模塊中可能要這樣寫:

// 獲取Content-Type
request.getHeader("Content-Type")

// 設(shè)置Content-Type
response.setHeader("Content-Type", "application/json")
response.setHeader("Content-Length", "18")
// 或者,忽略前邊的statusCode,設(shè)置多個(gè)Header
response.writeHead(200, {
  "Content-Type": "application/json",
  "Content-Length": "18"
})

而在koa中可以這樣處理:

// 獲取Content-Type
context.request.get("Content-Type")

// 設(shè)置Content-Type
context.response.set({
  "Content-Type": "application/json",
  "Content-Length": "18"
})

簡(jiǎn)化了一些針對(duì)requestresponse的操作,將這些封裝在了request.jsresponse.js文件中。
但同時(shí)這會(huì)帶來(lái)一個(gè)使用上的困擾,這樣封裝以后其實(shí)獲取或者設(shè)置header變得層級(jí)更深,需要通過(guò)context找到request、response,然后才能進(jìn)行操作。
所以,koa使用了node-delegates來(lái)進(jìn)一步簡(jiǎn)化這些步驟,將request.get、response.set通通代理到context上。
也就是說(shuō),代理后的操作是這樣子的:

context.get("Content-Type")

// 設(shè)置Content-Type
context.set({
  "Content-Type": "application/json",
  "Content-Length": "18"
})

這樣就變得很清晰了,獲取Header,設(shè)置Header,再也不會(huì)擔(dān)心寫成request.setHeader,一氣呵成,通過(guò)context.js來(lái)整合request.jsresponse.js的行為。
同時(shí)context.js也會(huì)提供一些其他的工具函數(shù),例如Cookie之類的操作。

application引入contextcontext中又整合了requestresponse的功能,四個(gè)文件的作用已經(jīng)很清晰了:

file desc
applicaiton 中間件的管理、http.createServer的回調(diào)處理,生成Context作為本次請(qǐng)求的參數(shù),并調(diào)用中間件
request 針對(duì)http.createServer -> request功能上的封裝
response 針對(duì)http.createServer -> response功能上的封裝
context 整合requestresponse的部分功能,并提供一些額外的功能

而在代碼結(jié)構(gòu)上,只有application對(duì)外的koa是采用的Class的方式,其他三個(gè)文件均是拋出一個(gè)普通的Object。

拿一個(gè)完整的流程來(lái)解釋 創(chuàng)建服務(wù)

首先,我們需要?jiǎng)?chuàng)建一個(gè)http服務(wù),在koa2.x中創(chuàng)建服務(wù)與koa1.x稍微有些區(qū)別,要求使用實(shí)例化的方式來(lái)進(jìn)行創(chuàng)建:

const app = new Koa()

而在實(shí)例化的過(guò)程中,其實(shí)koa只做了有限的事情,創(chuàng)建了幾個(gè)實(shí)例屬性。
將引入的contextrequest以及response通過(guò)Object.create拷貝的方式放到實(shí)例中。

this.middleware = [] // 最關(guān)鍵的一個(gè)實(shí)例屬性

// 用于在收到請(qǐng)求后創(chuàng)建上下文使用
this.context = Object.create(context)
this.request = Object.create(request)
this.response = Object.create(response)

在實(shí)例化完成后,我們就要進(jìn)行注冊(cè)中間件來(lái)實(shí)現(xiàn)我們的業(yè)務(wù)邏輯了,上邊也提到了,koa僅用作一個(gè)中間件的整合以及請(qǐng)求的監(jiān)聽。
所以不會(huì)像express那樣提供router.get、router.post之類的操作,僅僅存在一個(gè)比較接近http.createServeruse()。
接下來(lái)的步驟就是注冊(cè)中間件并監(jiān)聽一個(gè)端口號(hào)啟動(dòng)服務(wù):

const port = 8000

app.use(async (ctx, next) => {
  console.time("request")
  await next()
  console.timeEnd("request")
})
app.use(async (ctx, next) => {
  await next()
  ctx.body = ctx.body.toUpperCase()
})

app.use(ctx => {
  ctx.body = "Hello World"
})

app.use(ctx => {
  console.log("never output")
})

app.listen(port, () => console.log(`Server run as http://127.0.0.1:${port}`))

在翻看application.js的源碼時(shí),可以看到,暴露給外部的方法,常用的基本上就是uselisten。
一個(gè)用來(lái)加載中間件,另一個(gè)用來(lái)監(jiān)聽端口并啟動(dòng)服務(wù)。

而這兩個(gè)函數(shù)實(shí)際上并沒(méi)有過(guò)多的邏輯,在use中僅僅是判斷了傳入的參數(shù)是否為一個(gè)function,以及在2.x版本針對(duì)Generator函數(shù)的一些特殊處理,將其轉(zhuǎn)換為了Promise形式的函數(shù),并將其push到構(gòu)造函數(shù)中創(chuàng)建的middleware數(shù)組中。
這個(gè)是從1.x過(guò)渡到2.x的一個(gè)工具,在3.x版本將直接移除Generator的支持。
其實(shí)在koa-convert內(nèi)部也是引用了cokoa-compose來(lái)進(jìn)行轉(zhuǎn)化,所以也就不再贅述。

而在listen中做的事情就更簡(jiǎn)單了,只是簡(jiǎn)單的調(diào)用http.createServer來(lái)創(chuàng)建服務(wù),并監(jiān)聽對(duì)應(yīng)的端口之類的操作。
有一個(gè)細(xì)節(jié)在于,createServer中傳入的是koa實(shí)例的另一個(gè)方法調(diào)用后的返回值callback,這個(gè)方法才是真正的回調(diào)處理,listen只是http模塊的一個(gè)快捷方式。
這個(gè)是為了一些用socket.io、https或者一些其他的http模塊來(lái)進(jìn)行使用的。
也就意味著,只要是可以提供與http模塊一致的行為,koa都可以很方便的接入。

listen(...args) {
  debug("listen")
  const server = http.createServer(this.callback())
  return server.listen(...args)
}
使用koa-compose合并中間件

所以我們就來(lái)看看callback的實(shí)現(xiàn):

callback() {
  const fn = compose(this.middleware)

  if (!this.listenerCount("error")) this.on("error", this.onerror)

  const handleRequest = (req, res) => {
    const ctx = this.createContext(req, res)
    return this.handleRequest(ctx, fn)
  }

  return handleRequest
}

在函數(shù)內(nèi)部的第一步,就是要處理中間件,將一個(gè)數(shù)組中的中間件轉(zhuǎn)換為我們想要的洋蔥模型格式的。
這里就用到了比較核心的koa-compose

其實(shí)它的功能上與co類似,只不過(guò)把co處理Generator函數(shù)那部分邏輯全部去掉了,本身co的代碼也就是一兩百行,所以精簡(jiǎn)后的koa-compose代碼僅有48行。

我們知道,async函數(shù)實(shí)際上剝開它的語(yǔ)法糖以后是長(zhǎng)這個(gè)樣子的:

async function func () {
  return 123
}

// ==>

function func () {
  return Promise.resolve(123)
}
// or
function func () {
  return new Promise(resolve => resolve(123))
}

所以拿上述use的代碼舉例,實(shí)際上koa-compose拿到的是這樣的參數(shù):

[
  function (ctx, next) {
    return new Promise(resolve => {
      console.time("request")
      next().then(() => {
        console.timeEnd("request")
        resolve()
      })
    })
  },
  function (ctx, next) {
    return new Promise(resolve => {
      next().then(() => {
        ctx.body = ctx.body.toUpperCase()
        resolve()
      })
    })
  },
  function (ctx, next) {
    return new Promise(resolve => {
      ctx.body = "Hello World"
      resolve()
    })
  },
  function (ctx, next) {
    return new Promise(resolve => {
      console.log("never output")
      resolve()
    })
  }
]

就像在第四個(gè)函數(shù)中輸出表示的那樣,第四個(gè)中間件不會(huì)被執(zhí)行,因?yàn)榈谌齻€(gè)中間件并沒(méi)有調(diào)用next,所以實(shí)現(xiàn)類似這樣的一個(gè)洋蔥模型是很有意思的一件事情。
首先拋開不變的ctx不談,洋蔥模型的實(shí)現(xiàn)核心在于next的處理。
因?yàn)?b>next是你進(jìn)入下一層中間件的鑰匙,只有手動(dòng)觸發(fā)以后才會(huì)進(jìn)入下一層中間件。
然后我們還需要保證next要在中間件執(zhí)行完畢后進(jìn)行resolve,返回到上一層中間件:

return function (context, next) {
  // last called middleware #
  let index = -1
  return dispatch(0)
  function dispatch (i) {
    if (i <= index) return Promise.reject(new Error("next() called multiple times"))
    index = i
    let fn = middleware[i]
    if (i === middleware.length) fn = next
    if (!fn) return Promise.resolve()
    try {
      return Promise.resolve(fn(context, dispatch.bind(null, i + 1)))
    } catch (err) {
      return Promise.reject(err)
    }
  }
}

所以明確了這兩點(diǎn)以后,上邊的代碼就會(huì)變得很清晰:

next用來(lái)進(jìn)入下一個(gè)中間件

next在當(dāng)前中間件執(zhí)行完成后會(huì)觸發(fā)回調(diào)通知上一個(gè)中間件,而完成的前提是內(nèi)部的中間件已經(jīng)執(zhí)行完成(resolved)

可以看到在調(diào)用koa-compose以后實(shí)際上會(huì)返回一個(gè)自執(zhí)行函數(shù)。
在執(zhí)行函數(shù)的開頭部分,判斷當(dāng)前中間件的下標(biāo)來(lái)防止在一個(gè)中間件中多次調(diào)用next。
因?yàn)槿绻啻握{(diào)用next,就會(huì)導(dǎo)致下一個(gè)中間件的多次執(zhí)行,這樣就破壞了洋蔥模型。

其次就是compose實(shí)際上提供了一個(gè)在洋蔥模型全部執(zhí)行完畢后的回調(diào),一個(gè)可選的參數(shù),實(shí)際上作用與調(diào)用compose后邊的then處理沒(méi)有太大區(qū)別。

以及上邊提到的,next是進(jìn)入下一個(gè)中間件的鑰匙,可以在這一個(gè)柯里化函數(shù)的應(yīng)用上看出來(lái):

Promise.resolve(fn(context, dispatch.bind(null, i + 1)))

將自身綁定了index參數(shù)后傳入本次中間件,作為調(diào)用函數(shù)的第二個(gè)參數(shù),也就是next,效果就像調(diào)用了dispatch(1),這樣就是一個(gè)洋蔥模型的實(shí)現(xiàn)。
fn的調(diào)用如果是一個(gè)async function,那么外層的Promise.resolve會(huì)等到內(nèi)部的async執(zhí)行resolve以后才會(huì)觸發(fā)resolve,例如這樣:

Promise.resolve(new Promise(resolve => setTimeout(resolve, 500))).then(console.log) // 500ms以后才會(huì)觸發(fā) console.log

P.S. 一個(gè)從koa1.x切換到koa2.x的暗坑,co會(huì)對(duì)數(shù)組進(jìn)行特殊處理,使用Promise.all進(jìn)行包裝,但是koa2.x沒(méi)有這樣的操作。
所以如果在中間件中要針對(duì)一個(gè)數(shù)組進(jìn)行異步操作,一定要手動(dòng)添加Promise.all,或者說(shuō)等草案中的await*。

// koa1.x
yield [Promise.resolve(1), Promise.resolve(2)]              // [1, 2]

// koa2.x
await [Promise.resolve(1), Promise.resolve(2)]              // [, ]

// ==>
await Promise.all([Promise.resolve(1), Promise.resolve(2)]) // [1, 2]
await* [Promise.resolve(1), Promise.resolve(2)]             // [1, 2]
接收請(qǐng)求,處理返回值

經(jīng)過(guò)上邊的代碼,一個(gè)koa服務(wù)已經(jīng)算是運(yùn)行起來(lái)了,接下來(lái)就是訪問(wèn)看效果了。
在接收到一個(gè)請(qǐng)求后,koa會(huì)拿之前提到的contextrequest、response來(lái)創(chuàng)建本次請(qǐng)求所使用的上下文。
koa1.x中,上下文是綁定在this上的,而在koa2.x是作為第一個(gè)參數(shù)傳入進(jìn)來(lái)的。
個(gè)人猜測(cè)可能是因?yàn)?b>Generator不能使用箭頭函數(shù),而async函數(shù)可以使用箭頭函數(shù)導(dǎo)致的吧:) 純屬個(gè)人YY

總之,我們通過(guò)上邊提到的三個(gè)模塊創(chuàng)建了一個(gè)請(qǐng)求所需的上下文,基本上是一通兒賦值,代碼就不貼了,沒(méi)有太多邏輯,就是有一個(gè)小細(xì)節(jié)比較有意思:

request.response = response
response.request = request

讓兩者之間產(chǎn)生了一個(gè)引用關(guān)系,既可以通過(guò)request獲取到response,也可以通過(guò)response獲取到request。
而且這是一個(gè)遞歸的引用,類似這樣的操作:

let obj = {}

obj.obj = obj

obj.obj.obj.obj === obj // true

同時(shí)如上文提到的,在context創(chuàng)建的過(guò)程中,將一大批的requestresponse的屬性、方法代理到了自身,有興趣的可以自己翻看源碼(看著有點(diǎn)暈):koa.js | context.js
這個(gè)delegate的實(shí)現(xiàn)也算是比較簡(jiǎn)單,通過(guò)取出原始的屬性,然后存一個(gè)引用,在自身的屬性被觸發(fā)時(shí)調(diào)用對(duì)應(yīng)的引用,類似一個(gè)民間版的Proxy吧,期待后續(xù)能夠使用Proxy代替它。

然后我們會(huì)將生成好的context作為參數(shù)傳入koa-compose生成的洋蔥中去。
因?yàn)闊o(wú)論何種情況,洋蔥肯定會(huì)返回結(jié)果的(出錯(cuò)與否),所以我們還需要在最后有一個(gè)finished的處理,做一些類似將ctx.body轉(zhuǎn)換為數(shù)據(jù)進(jìn)行輸出之類的操作。

koa使用了大量的get、set訪問(wèn)器來(lái)實(shí)現(xiàn)功能,例如最常用的ctx.body = "XXX",它是來(lái)自responseset body
這應(yīng)該是request、response中邏輯最復(fù)雜的一個(gè)方法了。
里邊要處理很多東西,例如在body內(nèi)容為空時(shí)幫助你修改請(qǐng)求的status code為204,并移除無(wú)用的headers。
以及如果沒(méi)有手動(dòng)指定status code,會(huì)默認(rèn)指定為200。
甚至還會(huì)根據(jù)當(dāng)前傳入的參數(shù)來(lái)判斷content-type應(yīng)該是html還是普通的text

// string
if ("string" == typeof val) {
  if (setType) this.type = /^s*

以及還包含針對(duì)流(Stream)的特殊處理,例如如果要用koa實(shí)現(xiàn)靜態(tài)資源下載的功能,也是可以直接調(diào)用ctx.body進(jìn)行賦值的,所有的東西都已經(jīng)在response.js中幫你處理好了:

// stream
if ("function" == typeof val.pipe) {
  onFinish(this.res, destroy.bind(null, val))
  ensureErrorHandler(val, err => this.ctx.onerror(err))

  // overwriting
  if (null != original && original != val) this.remove("Content-Length")

  if (setType) this.type = "bin"
  return
}

// 可以理解為是這樣的代碼
let stream = fs.createReadStream("package.json")
ctx.body = stream

// set body中的處理
onFinish(res, () => {
  destory(stream)
})

stream.pipe(res) // 使response接收流是在洋蔥模型完全執(zhí)行完以后再進(jìn)行的

onFinish用來(lái)監(jiān)聽流是否結(jié)束、destory用來(lái)關(guān)閉流

其余的訪問(wèn)器基本上就是一些常見操作的封裝,例如針對(duì)querystring的封裝。
在使用原生http模塊的情況下,處理URL中的參數(shù),是需要自己引入額外的包進(jìn)行處理的,最常見的是querystring。
koa也是在內(nèi)部引入的該模塊。
所以對(duì)外拋出的query大致是這個(gè)樣子的:

get query() {
  let query = parse(this.req).query
  return qs.parse(query)
}

// use
let { id, name } = ctx.query // 因?yàn)?get query也被代理到了context上,所以可以直接引用

parse為parseurl庫(kù),用來(lái)從request中提出query參數(shù)

亦或者針對(duì)cookies的封裝,也是內(nèi)置了最流行的cookies。
在第一次觸發(fā)get cookies時(shí)才去實(shí)例化Cookie對(duì)象,將這些繁瑣的操作擋在用戶看不到的地方:

get cookies() {
  if (!this[COOKIES]) {
    this[COOKIES] = new Cookies(this.req, this.res, {
      keys: this.app.keys,
      secure: this.request.secure
    })
  }
  return this[COOKIES]
}

set cookies(_cookies) {
  this[COOKIES] = _cookies
}

所以在koa中使用Cookie就像這樣就可以了:

this.cookies.get("uid")

this.cookies.set("name", "Niko")

// 如果不想用cookies模塊,完全可以自己賦值為自己想用的cookie
this.cookies = CustomeCookie

this.cookies.mget(["uid", "name"])

這是因?yàn)樵?b>get cookies里邊有判斷,如果沒(méi)有一個(gè)可用的Cookie實(shí)例,才會(huì)默認(rèn)去實(shí)例化。

洋蔥模型執(zhí)行完成后的一些操作

koa的一個(gè)請(qǐng)求流程是這樣的,先執(zhí)行洋蔥里邊的所有中間件,在執(zhí)行完成以后,還會(huì)有一個(gè)回調(diào)函數(shù)。
該回調(diào)用來(lái)根據(jù)中間件執(zhí)行過(guò)程中所做的事情來(lái)決定返回給客戶端什么數(shù)據(jù)。
拿到ctx.body、ctx.status這些參數(shù)進(jìn)行處理。
包括前邊提到的流(Stream)的處理都在這里:

if (body instanceof Stream) return body.pipe(res) // 等到這里結(jié)束后才會(huì)調(diào)用我們上邊`set body`中對(duì)應(yīng)的`onFinish`的處理

同時(shí)上邊還有一個(gè)特殊的處理,如果為false則不做任何處理,直接返回:

if (!ctx.writable) return

其實(shí)這個(gè)也是response提供的一個(gè)訪問(wèn)器,這里邊用來(lái)判斷當(dāng)前請(qǐng)求是否已經(jīng)調(diào)用過(guò)end給客戶端返回了數(shù)據(jù),如果已經(jīng)觸發(fā)了response.end()以后,則response.finished會(huì)被置為true,也就是說(shuō),本次請(qǐng)求已經(jīng)結(jié)束了,同時(shí)訪問(wèn)器中還處理了一個(gè)bug,請(qǐng)求已經(jīng)返回結(jié)果了,但是依然沒(méi)有關(guān)閉套接字:

get writable() {
  // can"t write any more after response finished
  if (this.res.finished) return false

  const socket = this.res.socket
  // There are already pending outgoing res, but still writable
  // https://github.com/nodejs/node/blob/v4.4.7/lib/_http_server.js#L486
  if (!socket) return true
  return socket.writable
}

這里就有一個(gè)koaexpress對(duì)比的劣勢(shì)了,因?yàn)?b>koa采用的是一個(gè)洋蔥模型,對(duì)于返回值,如果是使用ctx.body = "XXX"來(lái)進(jìn)行賦值,這會(huì)導(dǎo)致最終調(diào)用response.end時(shí)在洋蔥全部執(zhí)行完成后再進(jìn)行的,也就是上邊所描述的回調(diào)中,而express就是在中間件中就可以自由控制何時(shí)返回?cái)?shù)據(jù):

// express.js
router.get("/", function (req, res) {
  res.send("hello world")

  // 在發(fā)送數(shù)據(jù)后做一些其他處理
  appendLog()
})

// koa.js
app.use(ctx => {
  ctx.body = "hello world"

  // 然而依然發(fā)生在發(fā)送數(shù)據(jù)之前
  appendLog()
})

不過(guò)好在還是可以通過(guò)直接調(diào)用原生的response對(duì)象來(lái)進(jìn)行發(fā)送數(shù)據(jù)的,當(dāng)我們手動(dòng)調(diào)用了response.end以后(response.finished === true),就意味著最終的回調(diào)會(huì)直接跳過(guò),不做任何處理。

app.use(ctx => {
  ctx.res.end("hello world")

  // 在發(fā)送數(shù)據(jù)后做一些其他處理
  appendLog()
})
異常處理

koa的整個(gè)請(qǐng)求,實(shí)際上還是一個(gè)Promise,所以在洋蔥模型后邊的監(jiān)聽不僅僅有resolve,對(duì)reject也同樣是有處理的。
期間任何一環(huán)出bug都會(huì)導(dǎo)致后續(xù)的中間件以及前邊等待回調(diào)的中間件終止,直接跳轉(zhuǎn)到最近的一個(gè)異常處理模塊。
所以,如果有類似接口耗時(shí)統(tǒng)計(jì)的中間件,一定要記得在try-catch中執(zhí)行next的操作:

app.use(async (ctx, next) => {
  try {
    await next()
  } catch (e) {
    console.error(e)
    ctx.body = "error" // 因?yàn)閮?nèi)部的中間件并沒(méi)有catch 捕獲異常,所以拋出到了這里
  }
})

app.use(async (ctx, next) => {
  let startTime = new Date()
  try {
    await next()
  } finally {
    let endTime = new Date() // 拋出異常,但是不影響這里的正常輸出
  }
})

app.use(ctx => Promise.reject(new Error("test")))

P.S. 如果異常被捕獲,則會(huì)繼續(xù)執(zhí)行后續(xù)的response

app.use(async (ctx, next) => {
  try {
    throw new Error("test")
  } catch (e) {
    await next()
  }
})

app.use(ctx => {
  ctx.body = "hello"
})

// curl 127.0.0.1 
// > hello

如果自己的中間件沒(méi)有捕獲異常,就會(huì)走到默認(rèn)的異常處理模塊中。
在默認(rèn)的異常模塊中,基本上是針對(duì)statusCode的一些處理,以及一些默認(rèn)的錯(cuò)誤顯示:

const code = statuses[err.status]
const msg = err.expose ? err.message : code
this.status = err.status
this.length = Buffer.byteLength(msg)
this.res.end(msg)

statuses是一個(gè)第三方模塊,包括各種http code的信息: statuses
建議在最外層的中間件都自己做異常處理,因?yàn)槟J(rèn)的錯(cuò)誤提示有點(diǎn)兒太難看了(純文本),自己處理跳轉(zhuǎn)到異常處理頁(yè)面會(huì)好一些,以及避免一些接口因?yàn)槟J(rèn)的異常信息導(dǎo)致解析失敗。

redirect的注意事項(xiàng)

在原生http模塊中進(jìn)行302的操作(俗稱重定向),需要這么做:

response.writeHead(302, {
  "Location": "redirect.html"
})
response.end()
// or
response.statusCode = 302
response.setHeader("Location", "redirect.html")
response.end()

而在koa中也有redirect的封裝,可以通過(guò)直接調(diào)用redirect函數(shù)來(lái)完成重定向,但是需要注意的是,調(diào)用完redirect之后并沒(méi)有直接觸發(fā)response.end(),它僅僅是添加了一個(gè)statusCodeLocation而已:

redirect(url, alt) {
  // location
  if ("back" == url) url = this.ctx.get("Referrer") || alt || "/"
  this.set("Location", url)

  // status
  if (!statuses.redirect[this.status]) this.status = 302

  // html
  if (this.ctx.accepts("html")) {
    url = escape(url)
    this.type = "text/html charset=utf-8"
    this.body = `Redirecting to ${url}.`
    return
  }

  // text
  this.type = "text/plain charset=utf-8"
  this.body = `Redirecting to ${url}.`
}

后續(xù)的代碼還會(huì)繼續(xù)執(zhí)行,所以建議在redirect之后手動(dòng)結(jié)束當(dāng)前的請(qǐng)求,也就是直接return,不然很有可能后續(xù)的status、body賦值很可能會(huì)導(dǎo)致一些詭異的問(wèn)題。

app.use(ctx => {
  ctx.redirect("https://baidu.com")

  // 建議直接return

  // 后續(xù)的代碼還在執(zhí)行
  ctx.body = "hello world"
  ctx.status = 200 // statusCode的改變導(dǎo)致redirect失效 
})
小記

koa是一個(gè)很好玩的框架,在閱讀源碼的過(guò)程中,其實(shí)也發(fā)現(xiàn)了一些小問(wèn)題:

多人合作維護(hù)一份代碼,確實(shí)能夠看出各人都有不同的編碼風(fēng)格,例如typeof val !== "string""number" == typeof code,很顯然的兩種風(fēng)格。2333

delegate的調(diào)用方式在屬性特別多的時(shí)候并不是很好看,一大長(zhǎng)串的鏈?zhǔn)秸{(diào)用,如果換成循環(huán)會(huì)更好看一下

但是,koa依然是一個(gè)很棒的框架,很適合閱讀源碼來(lái)進(jìn)行學(xué)習(xí),這些都是一些小細(xì)節(jié),無(wú)傷大雅。

總結(jié)一下koakoa-compose的作用:

koa 注冊(cè)中間件、注冊(cè)http服務(wù)、生成請(qǐng)求上下文調(diào)用中間件、處理中間件對(duì)上下文對(duì)象的操作、返回?cái)?shù)據(jù)結(jié)束請(qǐng)求

koa-compose 將數(shù)組中的中間件集合轉(zhuǎn)換為串行調(diào)用,并提供鑰匙(next)用來(lái)跳轉(zhuǎn)下一個(gè)中間件,以及監(jiān)聽next獲取內(nèi)部中間件執(zhí)行結(jié)束的通知

招人,招人

我司現(xiàn)在大量招人咯,前端、Node方向都有HC
公司名:Blued,坐標(biāo)帝都朝陽(yáng)雙井
主要技術(shù)棧是React,也會(huì)有機(jī)會(huì)玩ReactNative和Electron
Node方向8.x版本+koa 新項(xiàng)目會(huì)以TS為主
有興趣的小伙伴可以私聊我,或者:
email: [email protected]
wechat: github_jiasm

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/96421.html

相關(guān)文章

  • Koa源碼閱讀筆記(2) -- compose

    摘要:于是抱著知其然也要知其所以然的想法,開始閱讀的源代碼。問(wèn)題讀源代碼時(shí),自然是帶著諸多問(wèn)題的。源代碼如下在被處理完后,每當(dāng)有新請(qǐng)求,便會(huì)調(diào)用,去處理請(qǐng)求。接下來(lái)會(huì)繼續(xù)寫一些閱讀筆記,因?yàn)榭吹脑创a確實(shí)是獲益匪淺。 本筆記共四篇Koa源碼閱讀筆記(1) -- coKoa源碼閱讀筆記(2) -- composeKoa源碼閱讀筆記(3) -- 服務(wù)器の啟動(dòng)與請(qǐng)求處理Koa源碼閱讀筆記(4) -...

    roland_reed 評(píng)論0 收藏0
  • Koa2源碼閱讀筆記

    摘要:引言最近空閑時(shí)間讀了一下的源碼在閱讀的源碼的過(guò)程中,我的感受是代碼簡(jiǎn)潔思路清晰不得不佩服大神的水平。調(diào)用的時(shí)候就跟有區(qū)別使用必須使用來(lái)調(diào)用除了上面的的構(gòu)造函數(shù)外,還暴露了一些公用的,比如兩個(gè)常見的,一個(gè)是,一個(gè)是。 引言 最近空閑時(shí)間讀了一下Koa2的源碼;在閱讀Koa2(version 2.2.0)的源碼的過(guò)程中,我的感受是代碼簡(jiǎn)潔、思路清晰(不得不佩服大神的水平)。下面是我讀完之后...

    plus2047 評(píng)論0 收藏0
  • 中間件執(zhí)行模塊koa-Compose源碼分析

    摘要:原文博客地址,歡迎學(xué)習(xí)交流點(diǎn)擊預(yù)覽讀了下的源碼,寫的相當(dāng)?shù)木?jiǎn),遇到處理中間件執(zhí)行的模塊決定學(xué)習(xí)一下這個(gè)模塊的源碼。當(dāng)在下游沒(méi)有更多的中間件執(zhí)行后,堆棧將展開并且每個(gè)中間件恢復(fù)執(zhí)行其上游行為。 原文博客地址,歡迎學(xué)習(xí)交流:點(diǎn)擊預(yù)覽 讀了下Koa的源碼,寫的相當(dāng)?shù)木?jiǎn),遇到處理中間件執(zhí)行的模塊koa-Compose,決定學(xué)習(xí)一下這個(gè)模塊的源碼。 閱讀本文可以學(xué)到: Koa中間件的加載...

    imtianx 評(píng)論0 收藏0
  • koa源碼閱讀之目錄結(jié)構(gòu)輔助庫(kù)相關(guān)

    摘要:從一個(gè)對(duì)象里面提取需要的屬性這篇文章一直想寫了還想起那一夜我看到白天的代碼,實(shí)在太美了。 koa源碼lib主要文件有 application.js context.js request.js response.js application.js koa主要的邏輯處理代碼整個(gè)koa的處理 context.js 將req,res方法 掛載在這,生成ctx上下文對(duì)象 requests....

    sherlock221 評(píng)論0 收藏0
  • Koa源碼閱讀筆記(4) -- ctx對(duì)象

    摘要:本筆記共四篇源碼閱讀筆記源碼閱讀筆記源碼閱讀筆記服務(wù)器啟動(dòng)與請(qǐng)求處理源碼閱讀筆記對(duì)象起因前兩天終于把自己一直想讀的源代碼讀了一遍。首先放上關(guān)鍵的源代碼在上一篇源碼閱讀筆記服務(wù)器啟動(dòng)與請(qǐng)求處理中,我們已經(jīng)分析了的作用。 本筆記共四篇Koa源碼閱讀筆記(1) -- coKoa源碼閱讀筆記(2) -- composeKoa源碼閱讀筆記(3) -- 服務(wù)器の啟動(dòng)與請(qǐng)求處理Koa源碼閱讀筆記(4...

    ityouknow 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<