至少有两个重要的错误存在于XDomainRequest对象中,一个影响IE8,另一个影响IE9。
问题1 - 垃圾回收
在Internet Explorer 8中,XDomainRequest对象在调用send()但尚未完成时错误地受到垃圾回收。此错误的症状是开发人员工具的网络跟踪显示请求已“中止”,并且没有任何错误、超时或成功事件处理程序被调用。
典型的AJAX代码看起来像这样:
function sendCrossDomainAjax(url, successCallback, errorCallback) {
var xdr = new XDomainRequest();
xdr.open("get", url);
xdr.onload = function() { successCallback(); }
xdr.onerror = function() { errorCallback(); }
xdr.send();
}
在这个例子中,包含XDomainRequest的变量超出了作用域。如果用户不幸,在send()异步完成之前,IE的Javascript垃圾收集器将运行并且请求将被中止。即使可以将XDomainRequest对象捕获到OnLoad和OnError事件处理程序中,IE仍然会看到整个对象图没有对它的引用,并将其垃圾回收。IE应该"固定"对象直到完成。
你会注意到互联网上有很多其他讨论提到,在xdr.send();调用周围放置setTimeout会以某种方式"解决"神秘的XDomainRequest故障。这是一种补救措施,完全不正确。发生的所有事情都是XDomainRequest对象被"固定"到setTimeout闭包中,并且不会像往常那样快速进行垃圾回收。这并不能解决问题。
为了正确地解决这个问题,请确保XDomainRequest存储在全局变量中,直到请求完成。例如:
var pendingXDR = [];
function removeXDR(xdr) {
var index = pendingXDR.indexOf(xdr);
if (index >= 0) {
pendingXDR.splice(index, 1);
}
}
function sendCrossDomainAjax(url, successCallback, errorCallback) {
var xdr = new XDomainRequest();
xdr.open("get", url);
xdr.onload = function() {
removeXDR(xdr);
successCallback();
}
xdr.onerror = function() {
removeXDR(xdr);
errorCallback();
}
xdr.send();
pendingXDR.push(xdr);
}
问题2 - 缺少OnProgress事件处理程序
这个第二个问题已经被发现。Internet Explorer 9引入了一个XDomainRequest对象的回归,其中缺少(null)OnProgress事件处理程序会导致请求在尝试报告进度信息时中止。
对于快速请求,IE9从不尝试调用OnProgress事件处理程序,因此请求成功。某些条件,例如当IE由于太多打开的连接、网络延迟、服务器响应缓慢或请求或响应负载过大而延迟请求时,将导致IE9开始报告进度信息。
IE9尝试在未先检查其是否存在的情况下调用事件处理程序,XDomainRequest对象会崩溃并在内部销毁自身。
为解决此问题,请始终确保将事件处理程序附加到OnProgress上。考虑到这个错误,向所有对象的事件防御性地添加事件处理程序也不是一个坏主意。
var xdr = new XDomainRequest();
xdr.open("get", url);
xdr.onprogress = function() { };
其他问题
我看到有报告称,如果在调用 .open() 之前注册事件处理程序,XDomainRequest 可能会失败。为了保险起见,最好在 .open() 和 .send() 之间注册它们。我个人没有验证过这是否是一个实际的错误。
如果遇到“访问被拒绝”的错误,则是因为 XDomainRequest 不允许目标页面和主机页面之间存在不匹配的 URI 方案。换句话说,请不要从 HTTPS 页面调用 HTTP 资源。
请注意互联网上大多数 XDomainRequest 库。我查看了大部分流行的库,例如各种 jQuery AJAX 传输插件(包括此处链接的插件)。
当然,XDomainRequest 受到其所有限制和约束的影响。这些并非真正的错误,并且与替代方案(iframe 欺骗、Flash crossdomain.xml 传输)相比,它们并不是那么糟糕。
我已经在这里发布了一个新的jQuery AJAX XDomainRequest传输,使用公共领域许可证:https://github.com/ebickle/snippets/tree/master/javascript/xdomainrequest