在必须兼容IE8的老项目里,跨域数据交互往往只能依赖JSONP,因为IE8连最基本的XMLHttpRequest跨域都不支持。很多同学在写JSONP请求时顺手加上了timeout参数,期望请求超时后能走到error回调里做兜底处理,结果却发现:请求发出去了,服务端一直不响应,页面就这么干等着,error回调压根不执行。这个问题在Chrome、Firefox里可能不明显,但在IE8下几乎是必现的。本文就来剖析这个问题的根源,并给出一份可以直接落地的补丁代码。

一、先搞清楚JSONP在jQuery里的实现原理
JSONP并不是真正的Ajax,它没有用到XMLHttpRequest,而是通过动态创建<script>标签,把回调函数名拼在URL参数里发给服务端,服务端返回一段形如callback({...})的JavaScript代码,浏览器执行这段代码时调用预先注册的回调,从而完成数据传递。理解了这一点,就能明白为什么JSONP的超时处理天生比普通Ajax麻烦。
关键问题在于:script标签一旦发起加载,浏览器层面没有任何API可以中止它。普通Ajax请求可以调用xhr.abort()强行中断,而script请求做不到。所以jQuery对JSONP的超时处理只能靠setTimeout来模拟:定时器到期后,jQuery会触发一个内部的abort逻辑,把这次请求标记为失败,走error回调。
然而在jQuery 1.4.x到1.8.x的部分版本中,这套abort逻辑存在缺陷。具体表现是:超时定时器触发后,内部回调没有被正确标记为"已调用"状态,head里挂着的<script>标签也没有被移除,在某些执行路径下error回调甚至不会被调用。IE8的垃圾回收和事件模型与标准浏览器差异较大,把这个问题进一步放大了。你可以在IE8的开发者工具里观察DOM树,超时后那个script节点仍然挂在head下,请求状态也一直是未完成。
二、定位IE8下超时失效的具体原因
我们先用一段典型的请求代码复现问题。下面这段代码在IE8中,当接口超过5秒没有响应时,通常不会触发timeout后的error回调:
$.ajax({
url: 'http://api.ipipp.com/user/info',
dataType: 'jsonp',
timeout: 5000,
success: function (data) {
console.log('成功:' + data.name);
},
error: function (xhr, status, msg) {
// 在IE8下,超时后这里很可能不会被执行
console.log('失败:' + status);
}
});跟踪jQuery源码可以发现,JSONP的核心逻辑集中在jQuery.ajax的jsonp分支里。它做了三件事:第一,生成一个全局回调函数名(比如jsonp1289374992);第二,把这个回调挂到window上;第三,创建script标签插入head。超时控制则依赖一个内部定时器,到期后调用resolveValues或者abort相关逻辑。
问题出在老版本的实现里,超时定时器触发后对"请求是否已完成"的判断不够严谨。IE8下script标签的onload事件触发时机与标准浏览器不同步,加上jQuery没有在超时后显式移除script节点、解绑全局回调,导致后续状态判断混乱。打个比方:定时器响了,负责善后的人却先看了一眼"客人是不是已经到了",而IE8给出的状态信号又是含糊的,于是善后流程就卡死了。
三、修复补丁:手动接管超时与清理逻辑
最稳妥的修复方式,是绕开jQuery内部那套有缺陷的超时判断,自己管理定时器和script标签的生死。思路是:请求发出前记录一个唯一标识,超时后主动清理script节点、删除全局回调、手动触发失败逻辑。下面是完整的补丁封装:
(function (window, $) {
// 对JSONP做超时兜底的封装函数
window.jsonpSafe = function (opts) {
var callbackName = 'jsonp_cb_' + new Date().getTime();
var script = null;
var timer = null;
var isDone = false;
function cleanup() {
if (timer) { window.clearTimeout(timer); timer = null; }
if (script && script.parentNode) {
script.parentNode.removeChild(script);
}
script = null;
try { delete window[callbackName]; } catch (e) {
// IE8下delete可能失败,改用赋值undefined
window[callbackName] = undefined;
}
}
// 注册全局回调,服务端返回后由浏览器执行
window[callbackName] = function (data) {
if (isDone) { return; }
isDone = true;
cleanup();
if (opts.success) { opts.success(data); }
};
// 超时定时器:到期后强制判定为失败
timer = window.setTimeout(function () {
if (isDone) { return; }
isDone = true;
cleanup();
if (opts.error) {
opts.error({ status: 'timeout' }, 'timeout', 'jsonp request timeout');
}
}, opts.timeout || 10000);
// 动态创建script标签发起请求
script = document.createElement('script');
script.type = 'text/javascript';
script.src = opts.url + (opts.url.indexOf('?') > -1 ? '&' : '?')
+ 'callback=' + callbackName;
// IE8要求先设置onreadystatechange再插入节点才能可靠触发
script.onreadystatechange = function () {
if (this.readyState === 'loaded' || this.readyState === 'complete') {
// 成功路径由全局回调负责,这里只做兜底
if (!isDone && !window[callbackName]) {
isDone = true;
cleanup();
if (opts.error) { opts.error({}, 'parsererror', 'empty response'); }
}
}
};
document.getElementsByTagName('head')[0].appendChild(script);
};
})(window, jQuery);这个补丁有几个细节值得注意。首先是isDone标志位,它保证success和error互斥且只执行一次,避免IE8下事件乱序导致的重复回调。其次是cleanup函数里的删除逻辑,IE8对window对象的delete操作有已知缺陷,必须用try-catch包裹,失败时退化为赋值undefined。最后是script节点的移除,这一步不能省,否则每次请求都会在head里堆积一个死节点,长时间运行的页面会越来越卡。
如果你不想替换原有的$.ajax调用,也可以采用第二种方案:打猴子补丁(monkey patch)扩展jQuery本身,在发送JSONP前包一层自己的定时器,超时后手动调用fail回调并清理。这种方案侵入性小,但要小心与jQuery版本的内部实现耦合,升级jQuery时需要回归测试。对于还能继续维护的老系统,更推荐直接升级到jQuery 1.12.4,这个版本已经修复了JSONP超时判断的问题,同时仍然完整支持IE8。
四、修复后的验证与常见坑
补丁上线前,建议在IE8里做三类验证:一是服务端正常返回,确认success回调触发且head里script节点被清理;二是服务端延迟超过timeout,确认error回调触发且状态为timeout;三是服务端返回非法内容(比如HTML错误页),确认走parsererror兜底而不是静默失败。可以用一个故意sleep的测试接口来模拟第二种情况。
还有几个坑提前说一下。第一,不要在error回调里直接重试而不加次数限制,IE8下网络抖动较频繁,无限重试会造成请求风暴,建议配合指数退避并限制在3次以内。第二,如果页面存在多个JSONP并发,回调名一定要保证唯一,补丁里用时间戳生成名字在高并发下有小概率碰撞,可以再加一个随机数后缀。第三,服务端返回的Content-Type最好设置为application/javascript,IE8对text/html类型的脚本执行没有问题,但某些代理服务器会因此改写响应内容。
总结一下:IE8下jQuery JSONP超时失效的根源在于script请求无法被真正abort,而老版本jQuery的超时判断在这个浏览器上不够健壮。自己接管定时器、清理和回调分发,是目前最可控的修复路径。如果项目条件允许升级jQuery版本,优先升级;实在不行,就把上面这份补丁封装成公共工具函数,全局统一收口JSONP请求,后续排查问题也会轻松很多。
jQuery AjaxJSONP超时IE8兼容修改时间:2026-09-16 13:26:48