关于express和koa中的超时处理的一些分享
发布于 4 年前 作者 Yuki-Minakami 7350 次浏览 来自 分享

从某个话题中得到的灵感,这里做一个分享, http://cnodejs.org/topic/592fdc2f03dba3510d8a62a0#59364c3b538dbcaa6bc7dd48 楼主遇到一个问题,如何管理express中的超时?

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);

总结

Koa真是太好用了<br> 比起express来koa的优点数不胜数

https://cnodejs.org/topic/592fdc2f03dba3510d8a62a0#59364c3b538dbcaa6bc7dd48 http://cnodejs.org/topic/592fdc2f03dba3510d8a62a0#59364c3b538dbcaa6bc7dd48

4 回复

这样看起来代码量要少不少,问题也简单了。。。不过换框架重写代价大啊。。。慢慢来,我还得练练。。

完全不用这么做的,跟用kao 还是express没有关系,其他web框架也可能会出现这个问题。 可以hook底层方法避免:

var slice=Array.prototype.slice;
var wraphttpServerResponse=function(res){
   var finished=false;
   var end=res.end;
   var write=res.write;
   res.end=function(){
     if(this.finished || finished){
	   return -1;// do nothing
	 }
	 fnished=true;
	 return end.apply(this,slice.call(arguments));
   }
   res.write=function(){
     if(this.finished || finished){
	   return -1;// do nothing
	 }
	 return write.apply(this,slice.call(arguments));
   }
}

替换掉原有的底层方法,检测是否已经返回,如果已经返回就什么也不做,大概就是这么一个思路

@yyrdl 这也是一种思路,谢谢哥们的意见

回到顶部