如何处理 Safari 中 Ajax call 的 timeout
结论
timeout 不会让已经成功或失败的请求继续等待 30 秒。服务器一旦返回有效响应,jQuery 就会立即调用 success 或 error。只有请求在规定时间内没有完成,才会以 textStatus === 'timeout' 进入 error。
当前代码有一个明确的问题:always 不是 $.ajax() 的配置项。应该在配置对象中使用 complete,或者在 $.ajax() 返回的 jqXHR 上调用 .always()。
推荐修改
performAuthorizePaymentRequest: function (payment) {
return new Promise(function (resolve, reject) {
$.ajax({
type: 'POST',
url: encodedContextPath + '/checkout/authorise_order',
data: JSON.stringify(payment),
dataType: 'json',
contentType: 'application/json',
timeout: 30000,
success: function (data) {
resolve(data);
},
error: function (jqXHR, textStatus, errorThrown) {
if (textStatus === 'timeout') {
reject(new Error('Payment API request timed out'));
return;
}
reject(new Error(
errorThrown ||
jqXHR.responseText ||
'Payment API request failed'
));
},
complete: function () {
pageSpinner.end();
}
});
});
}
调用方也要同时处理成功和失败:
performAuthorizePaymentRequest(payment)
.then(function (response) {
showSuccessMessage(response);
})
.catch(function (error) {
showErrorMessage(error.message);
});
修改后,请求成功会立即执行 resolve();遇到 HTTP 错误、网络错误或 JSON 解析错误时,会立即执行 reject()。只有请求超过 30 秒仍未完成,才会报告 timeout。无论请求结果如何,complete 都会关闭 spinner。
更简洁的写法
$.ajax() 本身返回一个类似 Promise 的 jqXHR,通常不需要再套一层 new Promise():
performAuthorizePaymentRequest: function (payment) {
return $.ajax({
type: 'POST',
url: encodedContextPath + '/checkout/authorise_order',
data: JSON.stringify(payment),
dataType: 'json',
contentType: 'application/json',
timeout: 30000
}).always(function () {
pageSpinner.end();
});
}
调用时可以使用 jQuery 的回调:
performAuthorizePaymentRequest(payment)
.done(function (response) {
showSuccessMessage(response);
})
.fail(function (jqXHR, textStatus, errorThrown) {
if (textStatus === 'timeout') {
showErrorMessage('Payment API request timed out');
} else {
showErrorMessage(
errorThrown ||
jqXHR.responseText ||
'Payment API request failed'
);
}
});
如果后续代码要求这个函数返回原生 Promise,保留前一种包装方式更合适。
为什么看起来总是在等待 timeout
如果修改后问题仍然存在,可以重点检查下面几项。
后端是否真正结束了 HTTP 响应
“后端抛出异常”不等于浏览器已经收到错误响应。如果服务端捕获异常后既没有发送响应,也没有关闭连接,浏览器就只能继续等待,直到最终触发 timeout。
后端应及时返回明确的 HTTP 状态,例如 400、422 或 500,并结束响应。
返回内容是否为有效 JSON
代码中指定了:
dataType: 'json'
因此,响应内容必须是有效 JSON。空响应、HTML 错误页或格式错误的 JSON 都可能进入 error,此时 textStatus 通常是 parsererror。
可以暂时记录完整的错误信息来确认:
error: function (jqXHR, textStatus, errorThrown) {
console.error({
status: jqXHR.status,
textStatus: textStatus,
errorThrown: errorThrown,
responseText: jqXHR.responseText
});
}
调用方是否延迟了 UI 更新
resolve() 只表示 Promise 已经完成。成功消息何时显示,还取决于调用方的 .then()、.done() 以及后续业务代码。如果后面还有定时器或尚未完成的 Promise 链,延迟未必来自 $.ajax()。
注意事项
timeout: 30000的单位是毫秒,也就是 30 秒。- 前端 timeout 只会停止等待并中止客户端请求,不能保证后端已经停止处理付款。支付接口必须使用幂等键或查询订单状态,避免超时重试导致重复扣款。
- Safari 不会改变 jQuery 的基本回调规则。如果这里还涉及 Apple Pay 的
completePayment()时限,需要在成功、失败和超时三个分支中及时结束对应的支付流程。 pageSpinner.end()应放在complete中,或者通过返回值调用.always()。不能把always直接写进$.ajax()的配置对象。
备注:内容仅供参考。
