1. 程式人生 > >express和koa中的超時處理

express和koa中的超時處理

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的優點數不勝數