關(guān)鍵詞: Koa router、函數(shù)式編程、 中間件
問題描述:
在開發(fā)微信公眾號時,微信服務(wù)器與我自己的服務(wù)頻繁的交互。微信的服務(wù)器對我的服務(wù)器目前也僅僅有兩種方式: 一種是Get 請求另外一種是Post 請求。于是我在設(shè)計Koa 的router 時,想把所有關(guān)于微信服務(wù)器與我的服務(wù)器交互的邏輯統(tǒng)一寫在一個中間件中。如下:
export const router = app => {
const router = new Router()
router.all('/wechat-hear', async (ctx, next) => {
// call wetchat middleware
wechatMiddle(config.wechat, reply)(ctx, next)
})
app
.use(router.routes())
.use(router.allowedMethods())
}
明顯可以看出wechatMiddle 利用了函數(shù)式編程,這樣的設(shè)計自認(rèn)為代碼的邏輯更強(qiáng)了。但是在測試時總是出現(xiàn)下面的exception:
(node:41169) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): AssertionError [ERR_ASSERTION]: headers have already been sent
下面時中間件的代碼:
export default function (opts, reply) {
return async function(ctx, next) {
// Get wechat access token,.
if (ctx.method === 'GET') {
// do validation
} else if (ctx.method === 'POST') {
// do validation
}
// parse request from wchat to object
const data = await getRawBody(ctx.req, {
length: ctx.length,
limit: '1mb',
encoding: ctx.charset
})
const content = await util.parseXML (data)
var xml =
`<xml>
<ToUserName><![CDATA[${content.xml.FromUserName[0]}]]></ToUserName>
<FromUserName><![CDATA[${content.xml.ToUserName[0]}]]></FromUserName>
</xml>`
ctx.body = xml
ctx.status = 200
ctx.type = 'application/xml'
}
}
踩過的坑:
- 首先根據(jù)exception的信息和bing 搜索后的結(jié)果,初步認(rèn)為時由于Koa 的ctx(上下文對象)已經(jīng)返回但是代碼再一次返回。
- 在代碼的最后三句,我看到連續(xù)對上下文(ctx)賦值了三次。盡管我估計不會時這里出現(xiàn)的問題,但是我還是把其中的兩個ctx 賦值的語句注釋掉,程序出現(xiàn)exception。
- 由于Koa 引入了async/await 優(yōu)雅的實現(xiàn)了nodejs 的異步問題,當(dāng)前這個exception 也很有可能時異步的問題,中間件返回函數(shù)我是用async 關(guān)鍵字標(biāo)記了,中間件中的關(guān)鍵方法我也用await 關(guān)鍵字標(biāo)記了。因此我自認(rèn)為不是異步的問題,此時心中開始抓狂了,把許多代碼大片注釋并調(diào)試。此刻就像無頭的蒼蠅再亂撞,希望可以碰到root cause。這個過程浪費了大量的時間和精力, 依舊一無所獲。
- 最后我決定取消對router koa中間件的封裝,此時發(fā)現(xiàn)了問題,在router koa 類中,我沒有在調(diào)用中間件時對他使用await關(guān)鍵字標(biāo)記。
正確如下:
router.all('/wechat-hear', async (ctx, next) => {
// call wetchat middleware
await wechatMiddle(config.wechat, reply)(ctx, next)
})
坑后總結(jié):
前兩步思路還是正確的,第三步上半部分也沒有問題,但是在后來暴力調(diào)試勞力傷神這一點要總結(jié),如果在第三步的下半部分可以稍微王router koa中調(diào)用中間件的方法思考下,這個問題則可以很快完美的解決了。