在传统的jQuery项目中,Ajax请求的异常处理往往散落在各个业务代码里:有的接口写了error回调,有的忘了写,页面一旦出现网络波动或接口报错,用户看到的可能是一片空白,或者干脆没有任何反馈。其实jQuery早就为我们提供了一套全局的钩子机制,其中beforeSend和complete是最常用的两个切入点,配合$.ajaxSetup和$.ajaxError,完全可以搭建一个集中式的异常处理层,让所有请求的错误都能被统一捕获、统一提示、统一记录。这篇文章就来详细拆解这套方案的具体实现。

一、beforeSend与complete的执行时机与参数含义
要设计全局处理策略,先得弄清楚这两个钩子到底在什么时候被触发。beforeSend在请求发送之前执行,此时XMLHttpRequest对象已经创建但还没有建立连接,函数签名是beforeSend(xhr, settings),第一个参数是原生的XHR对象,第二个参数是本次请求的配置对象。这个阶段最大的价值在于可以修改请求头,比如统一注入token,也可以直接返回false来取消请求,这个特性非常适合做登录态校验。
complete则在请求结束时触发,无论成功还是失败都会执行,签名为complete(xhr, textStatus)。很多人容易把它和error混淆:error只处理请求失败的情况,而complete是收尾钩子,负责清理工作,比如关闭loading遮罩、还原按钮状态。正因为complete必然执行,它是做资源回收的最佳位置,可以避免某个分支忘记关闭遮罩导致页面卡死的问题。
还需要注意textStatus这个参数,它可能的值包括success、error、timeout、abort、parsererror等。在complete里根据这个值分支判断,就能区分出超时、解析失败等不同场景,比单纯依赖error回调拿到的信息更细。
二、基于ajaxSetup搭建统一的全局处理层
jQuery提供了$.ajaxSetup方法,可以为之后所有的Ajax请求设置默认配置,钩子函数也可以在这里统一定义。我们把token注入、loading管理、异常分类都收敛到一个文件里,业务代码只需要关心成功回调。
$.ajaxSetup({
timeout: 15000, // 全局超时15秒
beforeSend: function(xhr, settings) {
// 统一注入认证token
var token = localStorage.getItem('token');
if (token) {
xhr.setRequestHeader('X-Auth-Token', token);
}
// 未登录直接拦截请求
if (!token && settings.needAuth !== false) {
window.location.href = '/login.html';
return false; // 返回false取消本次请求
}
// 开启loading遮罩(可按需排除某些请求)
if (!settings.silent) {
showLoading();
}
},
complete: function(xhr, textStatus) {
hideLoading();
if (textStatus === 'timeout') {
showError('请求超时,请稍后重试');
} else if (textStatus === 'parsererror') {
showError('服务器返回数据格式异常');
}
}
});这段代码里有几个细节值得展开。第一,settings对象就是调用$.ajax时传入的配置,可以在业务请求里附加自定义标记,比如silent: true表示这个请求不需要loading,这种约定式的扩展让全局逻辑具备灵活性。第二,return false会中止请求,但要注意它不会触发error和complete回调,所以如果做了拦截,loading相关逻辑也要自己处理干净,否则会出现遮罩关不掉的情况。
第三点是GET请求的缓存问题。如果希望全局禁用缓存,可以在beforeSend里给settings.url追加时间戳参数,或者统一设置cache: false,避免IE等环境下拿到旧数据造成诡异bug。
三、用全局事件捕获HTTP错误与业务错误
ajaxSetup只覆盖了发送前后两个时点,真正的错误分类还得靠jQuery的全局事件。$(document).ajaxError会在任何请求失败时触发,参数中包含event、xhr、settings和thrownError,这里可以拿到HTTP状态码做统一分发。
$(document).ajaxError(function(event, xhr, settings, thrownError) {
var status = xhr.status;
var msg;
switch (status) {
case 0:
msg = '网络连接异常,请检查网络后重试';
break;
case 400:
msg = '请求参数错误';
break;
case 401:
msg = '登录已过期,请重新登录';
// 跳转登录页前先清理本地凭证
localStorage.removeItem('token');
window.location.href = '/login.html';
return;
case 403:
msg = '没有访问该资源的权限';
break;
case 404:
msg = '请求的接口不存在';
break;
case 500:
msg = '服务器内部错误,请稍后重试';
break;
default:
msg = '请求失败,错误码:' + status;
}
showError(msg);
// 上报错误日志,方便排查线上问题
if (window.logger) {
window.logger.report({
url: settings.url,
status: status,
detail: thrownError
});
}
});status为0通常意味着请求根本没有到达服务器,常见原因是网络断开、跨域被拦截或者请求被abort,这个场景要单独处理,否则用户看到一串英文报错会一头雾水。401的处理要格外小心,如果页面同时发出多个请求,可能会触发多次跳转,建议加一个节流标记,比如设置一个全局变量记录是否正在跳转,避免重复执行。
除了HTTP层面的错误,很多后端习惯返回200状态码加业务错误码的结构,这类错误ajaxError是捕获不到的,需要在complete或者统一的success包装层里解析响应体。可以在complete中读取xhr.responseJSON,判断其中的业务状态字段,做出相应的提示或处理,这样HTTP错误和业务错误就都有了着落。
四、封装一个更完善的请求工具函数
直接让业务代码调用$.ajax,时间长了容易出现参数五花八门的情况。更推荐的做法是再包一层工具函数,把默认行为固化下来,同时保留覆盖能力。
var request = function(options) {
var deferred = $.Deferred();
$.ajax($.extend(true, {
type: 'POST',
dataType: 'json',
contentType: 'application/json'
}, options)).done(function(res) {
if (res.code === 0) {
deferred.resolve(res.data);
} else {
showError(res.message || '操作失败');
deferred.reject(res);
}
}).fail(function() {
// HTTP错误已由全局ajaxError处理,这里只负责让链式调用中断
deferred.reject.apply(deferred, arguments);
});
return deferred.promise();
};
// 业务侧调用变得非常简洁
request({
url: '/api/user/list',
data: JSON.stringify({ page: 1 })
}).then(function(data) {
renderList(data);
});这种封装方式把三层职责划分得比较清晰:全局ajaxError负责HTTP错误,工具函数负责业务码解析,业务代码只处理成功数据。需要注意的是fail回调里不要再重复弹错误提示,否则同一错误会弹两次窗,体验很差。另外JSONP请求没有error回调支持,如果项目里还在用JSONP,超时和失败兜底要单独设计。
五、几个容易踩的坑
第一个坑是script标签等静默资源请求。页面里通过<script>加载的js文件不会经过jQuery的Ajax体系,全局钩子管不到它们,不要误以为所有网络请求都被覆盖了。第二个坑是并发请求的loading计数,如果两个请求同时发出,第一个完成就把遮罩关了,第二个还在跑,用户会看到界面闪烁。解决办法是用一个计数器,beforeSend时加一,complete时减一,减到零才隐藏遮罩。
(function() {
var count = 0;
function showLoading() {
count++;
$('#loadingMask').show();
}
function hideLoading() {
count = Math.max(0, count - 1);
if (count === 0) {
$('#loadingMask').hide();
}
}
window.showLoading = showLoading;
window.hideLoading = hideLoading;
})();第三个坑是同步请求的问题。如果设置了async: false,beforeSend里的某些异步操作(比如用Promise获取token)可能来不及生效,而且同步请求会阻塞浏览器渲染,新版浏览器已经直接在主线程禁用了这种用法,原则上应当避免。
整体来看,这套方案的思路和现在流行的axios拦截器非常相似,本质都是在请求生命周期的固定节点插入统一逻辑。即使项目还在维护jQuery老代码,用beforeSend加complete加全局事件的组合,也能低成本建立起规范的前端请求层,让异常处理从到处补丁变成集中管控,后续排查线上问题、统一错误文案、接入日志上报都会轻松很多。
jQuery AjaxbeforeSend全局异常处理修改时间:2026-09-10 01:54:39