0

我需要使用脚本标签 long polling来实现跨站comet http 服务器推送机制。(呼……)为此,我将脚本标签动态插入到 DOM 中,服务器发回简短的 js 脚本,这些脚本只是调用处理传入消息的本地回调函数。我试图找出一种方法将这些回调调用中的每一个与发送它的脚本标签相关联,以将传入的回复与其相应的请求相匹配。

显然,我可以简单地在 GET url 中包含一个请求 ID,然后将其返回到服务器生成的 js 脚本中,但这会产生一堆不必要的流量,并且不会让我觉得特别优雅或聪明。

我想做的是以某种方式将请求 ID 与我生成的脚本标记相关联,然后从从该脚本标记内部调用的回调函数中读出该请求 ID。这样,所有的请求管理都将保留在客户端上。

这让我想到了以下问题:有没有办法向浏览器询问当前正在执行的脚本标签的 DOM 元素,所以我可以使用标签元素将参数传递给包含的 javascript?

我找到了这个线程:

获取当前执行的、动态附加的脚本标签

这正是在问这个问题,但是接受的答案对我没有用,因为它仍然需要在服务器返回的 js 脚本中膨胀(在脚本中设置标记变量)并且它依赖于脚本的唯一文件名,我没有。

此外,这个线程是相关的:

如何引用加载当前执行脚本的脚本标签?

并且,除其他外,建议简单地抓取 DOM 中的最后一个脚本,因为它们是按顺序执行的。但这似乎只在页面加载时有效,而不是在动态添加脚本并且可能以独立于其插入的顺序完成加载的情况下。

有什么想法吗?

PS:我正在寻找一个仅限客户端的解决方案,即没有请求 ID 或唯一的回调函数名称或其他需要发送到服务器并由服务器处理的非有效负载数据。我希望服务器(理论上)能够返回两个 100% 相同的脚本,并且客户端仍然能够正确关联它们。

4

4 回答 4

0

可能有在脚本上使用 onload/onreadystate 事件的解决方案。我可以将这些事件传递给带有我的请求 ID 的闭包函数。然后,回调函数不会立即处理服务器回复,而是将其存储在全局变量中。然后,onload/onreadystate 处理程序获取最后存储的回复并用它知道的请求 ID 标记它,然后处理回复。

为此,我需要能够依赖事件的顺序。如果 onload 总是在相应的脚本标签完成执行立即执行,这将很好地工作。但是,如果我同时加载了两个标签并且它们同时返回,并且浏览器有可能同时执行这两个标签,然后执行这两个 onload/onreadystate 事件,那么我会以这种方式放弃一个回复。

有人对此有任何见解吗?

.

这里有一些代码来证明这一点:

function loadScript(url, requestID) {
  var script = document.createElement('script');
  script.setAttribute("src", url);
  script.setAttribute("type", "text/javascript");
  script.setAttribute("language", "javascript");
  script.onerror = script.onload = function() {
    script.onerror = script.onload = script.onreadystatechange = function () {}
    document.body.removeChild(script);
    completeRequest(requestID);
  }
  script.onreadystatechange = function () {
    if (script.readyState == 'loaded' || script.readyState == 'complete') {
      script.onerror = script.onload = script.onreadystatechange = function () {}
      document.body.removeChild(script);
      completeRequest(requestID);
    }
  }
  document.body.appendChild(script);
}

var lastReply;

function myCallback(reply) {
  lastReply = reply;
}

function completeRequest(requestID) {
  processReply(requestID, lastReply);
}

function processReply(requestID, reply) {
  // Do something
}

现在,服务器只返回表单的脚本

myCallback(message);

并且完全不需要担心请求 ID 等,并且始终可以使用相同的回调函数。

问题是:如果我有两个脚本“同时”返回,这是否可能导致以下调用顺序:

myCallback(message1);
myCallback(message2);
completeRequest(requestID1);
completeRequest(requestID2);

如果是这样,我会放弃对请求 1 的实际回复,并错误地将对请求 2 的回复与请求 1 关联起来。

于 2012-10-13T23:43:42.293 回答
0

我知道你想避免讨论改变方法,但这确实是你需要做的。

首先,添加到 DOM 以触发轮询请求的每个脚本标签都是一次性的,即每个脚本标签都需要在达到其目的后立即从 DOM 中删除。否则,您最终会用数百或更多死脚本标签淹没您的客户端 DOM。

一个很好的可比较示例是 jsonp 实现。您创建一个客户端命名函数,创建脚本标记以发出远程请求,并在请求中传递函数名称。响应脚本将 json 对象包装在具有名称的函数调用中,然后在返回时执行该函数并将 json 有效负载传递给您的函数。执行后,客户端函数随即被删除。jQuery 通过创建随机生成的名称来做到这一点(它们存在于全局上下文中,这实际上是该过程工作的唯一方式),然后在完成后删除回调函数。

关于长轮询,它的过程非常相似。本质上,响应函数调用不需要知道也不关心启动它的脚本标签。

让我们看一个示例脚本:

window.callback = function(obj){
    console.log(obj);   
}

setInterval(function(){

    var remote = document.createElement('script');
    remote.src = 'http://jsonip.com/callback';
    remote.addEventListener('load', function(){
        remote.parentNode.removeChild(remote);
    },false);

    document.querySelector('head').appendChild(remote);

}, 2000);​

该脚本不保留对脚本元素的引用,因为它们是一次性的。一旦他们的工作完成,他们就会立即被枪杀。

该示例可以稍微修改为不使用 setInterval,在这种情况下,您可以将 setInterval 替换为命名函数,并将逻辑添加到远程加载事件中,以在加载事件完成时触发该函数。这样,脚本标记事件之间的时间取决于服务器的响应时间,并且更接近实际的长轮询过程。

您可以通过使用排队系统来管理您的回调来进一步扩展它。如果您有不同的函数来响应返回的不同类型的数据,这可能会很有用。

或者,可能更好的是,在您的回调函数中登录,该函数处理从每个轮询返回的数据并在该点执行任何其他特定的客户端逻辑。这也意味着您只需要 1 个回调函数,并且可以避免创建随机生成的回调名称。

如果您需要更多帮助,请对任何具体问题发表评论,我可以更详细地介绍。

于 2012-10-13T23:35:28.580 回答
0

这绝对是可能的,但你需要一个小技巧。这是一种称为JSONP的常用技术。

在 JavaScript 中:

var get_a_unique_name = (function () {
    var counter = 0;
    return function () {
        counter += 1;
        return "function_" + counter;
    }
}()); // no magic, just a closure

var script = document.createElement("script");
var callback_name = get_a_unique_name();
script.src = "/request.php?id=12345&callback_name=" + callback_name;

// register the callback function globally
window[callback_name] = function (the_data) {
    console.log(the_data);
    handle_data(the_data); // implement this function
};

// add the script
document.head.appendChild(script);

您可以拥有的服务器端:

$callback_name = $_GET["callback_name"];
$the_data = handle_request($_GET["id"]); // implement handle_request
echo $callback_name . "(" . json_encode($the_data) . ");";
exit; // done

返回的脚本/request.php?id=12345&callback_name=XXX将如下所示:

function_0({ "hello": "world", "foo" : "bar" });
于 2012-10-13T23:31:09.563 回答
0

这应该很简单。每个服务器“连接”只有一个脚本元素,它可以很容易地存储在一个作用域的静态变量中。

function connect(nameOfCallback, eventCallback) {
    var script;
    window[nameOfCallback] = function() { // this is what the response invokes
        reload();
        eventCallback.call(null, arguments);
    };
    reload();

    function reload() {
        if (script && script.parentNode)
            script.parentNode.removeChild(script);
        script = document.createElement(script);
        script.src = "…";
        script.type = "text/javascript";
        document.head.appendChild(script);
        // you might use additional error handling, e.g. something like
        // script.onerror = reload;
        // but I guess you get the concept
    }
}
于 2012-10-14T00:08:22.900 回答