express和koa中的超時處理
阿新 • • 發佈:2019-02-07
express中的超時處理
現成的模組有connect-time,有一百五十個star,在中介軟體中已經算不少了,下面是一段使用connect-time的程式碼
var express = require('express')
var timeout = require('connect-timeout')
var app = express()
app.use(timeout('5s'))
//....一些中介軟體
app.use(haltOnTimedout)
// Add your routes here, etc.
function haltOnTimedout (req, res, next) {
if (!req.timedout) next()
}
app.listen(3000)
看起來很美好,然而實際上是有缺點的。
假設timeout的作用範圍裡中包含了一個名為query的中介軟體,長這個樣子
function query(req, res, next) {
setTimeout(function() {
res.send('respond with a resource');
}, 10000);
};
然後這樣呼叫timeout
app.use(timeout('5s'))
app.use(query)
app.use(haltOnTimedout)
當然實際中可能是一個數據庫操作或者別的,而且在某些情況下會因為阻塞讓執行時間變得很慢,上面的程式碼中會在10秒後將資料返回前端,而這時timeout已經返回了一個超時相應,於是程式就gg了,報出一個Can’t set headers after they are sent的錯誤。
這個其實不能怪中介軟體,要怪express本身的機制,query中介軟體雖然載入了,然而裡面的非同步卻不歸timeout管。
這個問題有辦法解決嗎?一個思路是當timeout觸發後,中斷後面的非同步操作,這個可以通過事件監聽實現,然而很麻煩。
Koa中的timeout
所以說還是老老實實地用koa,能用同步寫法就別寫回調。
koa也有一些管理timeout的中介軟體例如koa-timeout,然而兩三年沒更新了,用的還是koa1.x的版本,好在也不是很難寫,我就自己寫了一個
var koa2-timeout = async function(ctx ,next){
var tmr = null;
const timeout = 5000;//設定超時時間
await Promise.race([
new Promise(function(resolve, reject) {
tmr = setTimeout(function() {
var e = new Error('Request timeout');
e.status = 408;
reject(e);
}, timeout);
}),
new Promise(function(resolve, reject) {
//使用一個閉包來執行下面的中介軟體
(async function() {
await next();
clearTimeout(tmr);
resolve();
})();
})
])
}
核心思路是用promise.race來看定時器和後面全部的中介軟體哪個先跑完,
* 如果定時器先跑完,返回錯誤資訊,而且沒有呼叫後續的next()
* 如果後面的中介軟體先跑完….那就是正常執行沒毛病
下面是個呼叫的例子
var koa = require("koa");
var app = new koa();
app.use(koa2-timeout);
app.use(async (ctx,next)=>{
await myTimeout(1000);//這裡如果改成6000就會返回超時資訊
ctx.body = "end";
})
function myTimeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'done');
});
}
app.listen(3000);
其他的解決方案
仔細想想,還是有別的方案的,例如我們可以直接hack底層的res.end方法,只要在裡面增加一個是否已經響應過的flag判斷就好了。這裡不再講述
總結
Koa真是太好用了
比起express來koa的優點數不勝數