Javascript EventSource永久自动重新连接

Javascript EventSource永久自动重新连接,javascript,server-sent-events,server-push,Javascript,Server Sent Events,Server Push,我在项目前端使用JavaScript EventSource 有时,浏览器和服务器之间的连接失败或服务器崩溃。在这些情况下,EventSource会在3秒钟后尝试重新连接,如文档中所述 但它只尝试一次。如果仍然没有连接,EventSource将停止尝试重新连接,用户必须刷新浏览器窗口才能再次连接 我怎样才能防止这种行为?我需要EventSource尝试永远重新连接,而不仅仅是一次 浏览器是Firefox。我通过实现一个保持活力的系统来处理这个问题;如果浏览器重新连接对我来说是很好的,但我认为有时

我在项目前端使用JavaScript EventSource

有时,浏览器和服务器之间的连接失败或服务器崩溃。在这些情况下,EventSource会在3秒钟后尝试重新连接,如文档中所述

但它只尝试一次。如果仍然没有连接,EventSource将停止尝试重新连接,用户必须刷新浏览器窗口才能再次连接

我怎样才能防止这种行为?我需要EventSource尝试永远重新连接,而不仅仅是一次


浏览器是Firefox。

我通过实现一个保持活力的系统来处理这个问题;如果浏览器重新连接对我来说是很好的,但我认为有时它不会工作,而且不同的浏览器可能会有不同的行为

在我的书的第五章中,我花了相当多的篇幅讨论这个问题(Blantant plug,在O'Reilly找到:),但是如果您想要一个不需要任何后端更改的非常简单的解决方案,请设置一个全局计时器,该计时器将在30秒后触发。如果它触发,那么它将杀死EventSource对象并创建另一个对象。最后一个难题是在您的事件侦听器中:每次从后端获取数据时,杀死计时器并重新创建它。也就是说,只要您至少每30秒获得一次新数据,计时器将永远不会触发

下面是一些最基本的代码来说明这一点:

var keepAliveTimer = null;

function gotActivity(){
  if(keepaliveTimer != null)clearTimeout(keepaliveTimer);
  keepaliveTimer = setTimeout(connect, 30 * 1000);
}

function connect(){
  gotActivity();
  var es = new EventSource("/somewhere/");
  es.addEventListener('message', function(e){
    gotActivity();
    },false);
}
...
connect();
还要注意,我在连接之前调用了gotActivity()。否则,在有机会传递任何数据之前发生故障或死亡的连接将被忽略

顺便说一句,如果你也能改变后端,那么在25-30秒的安静之后发送一条空白消息(“心跳”)是值得的。否则,前端将不得不假设后端已经死亡。当然,如果您的服务器发送的定期消息间隔不超过25-30秒,则无需执行任何操作


如果您的应用程序依赖于Event Last Id头,请意识到您的keep-alive系统必须对此进行模拟;根据我的经验,如果出现网络级错误,浏览器通常会重新连接,但如果服务器响应HTTP错误(例如,状态500),则不会重新连接


我们的团队制作了一个简单的包装器库,用于在所有情况下重新连接:。也许这会有帮助。

下面,我将演示一种以合理速率永远重新连接的方法

这段代码使用了一个去盎司函数以及重新连接间隔加倍。在我的测试中,它运行良好。它以1秒、4秒、8秒、16秒的速度连接,最多64秒,并以相同的速率重试

function isFunction(functionToCheck) {
  return functionToCheck && {}.toString.call(functionToCheck) === '[object Function]';
}

function debounce(func, wait) {
    var timeout;
    var waitFunc;

    return function() {
        if (isFunction(wait)) {
            waitFunc = wait;
        }
        else {
            waitFunc = function() { return wait };
        }

        var context = this, args = arguments;
        var later = function() {
            timeout = null;
            func.apply(context, args);
        };
        clearTimeout(timeout);
        timeout = setTimeout(later, waitFunc());
    };
}

// reconnectFrequencySeconds doubles every retry
var reconnectFrequencySeconds = 1;
var evtSource;

var reconnectFunc = debounce(function() {
    setupEventSource();
    // Double every attempt to avoid overwhelming server
    reconnectFrequencySeconds *= 2;
    // Max out at ~1 minute as a compromise between user experience and server load
    if (reconnectFrequencySeconds >= 64) {
        reconnectFrequencySeconds = 64;
    }
}, function() { return reconnectFrequencySeconds * 1000 });

function setupEventSource() {
    evtSource = new EventSource(/* URL here */); 
    evtSource.onmessage = function(e) {
      // Handle even here
    };
    evtSource.onopen = function(e) {
      // Reset reconnect frequency upon successful connection
      reconnectFrequencySeconds = 1;
    };
    evtSource.onerror = function(e) {
      evtSource.close();
      reconnectFunc();
    };
}
setupEventSource();

好的,gotActivity调用应该在“new EventSource”之前,但经过一些小的修改后,所有调用都可以正常工作。@johnfound需要
gotActivity()
new EventSource()
调用之前出现什么问题?我对JS知之甚少。但是在我的测试中,如果新的事件源连接失败,那么gotActivity根本就没有执行,因此计时器没有重新启动以进行另一次尝试…@johnfound谢谢。我明白你的意思,我已经调整了我的答案。(我有过它,所以如果客户端不支持EventSource或我添加的任何回退,它不会浪费每30秒重试一次的精力;但这是一个更次要的问题。)设计不错。但是,如果服务器需要具有凭据的登录,并且服务器重新启动(使任何会话身份验证cookie无效),则事情会变得更复杂。:-)非常有用,Chrome 88不需要此功能,因为断开连接时readystate为0。FF 85没有,因为readystate在出现错误时似乎立即变为2(关闭)。此包装正确地尝试在readystate=2(FF)上重新打开,以便在两种浏览器上安全使用。