Firefox中XHR提交数据后重定向触发NS_BINDING_ABORTED错误排查
问题重现
在LAMP服务器上开发jsPsych实验,实验结束后通过XMLHttpRequest保存数据,代码中已等待xhr.readyState === XMLHttpRequest.DONE且xhr.status === 200确认数据保存成功后,才调用页面重定向函数,但Firefox中仍出现NS_BINDING_ABORTED错误。移除重定向逻辑后数据可正常保存,说明是重定向操作中断请求导致问题。相关代码如下:
saveData(save_url, data_dir, file_name, extension, redirect_html, redirectToNextPage); function saveData(save_url, data_dir, file_name, extension, redirect_html, callback) { var xhr = new XMLHttpRequest(); xhr.open('POST', save_url); // 'write_data.php' 是保存数据的PHP文件路径 xhr.setRequestHeader('Content-Type', 'application/json'); xhr.onreadystatechange = function() { if (xhr.readyState === XMLHttpRequest.DONE) { if (xhr.status === 200) { // 数据保存成功,触发回调 callback(redirect_html); } else { console.error('Error saving data: ' + xhr.status); } } }; xhr.send(JSON.stringify({file_name: file_name, extension: extension, data_dir: data_dir, data: jsPsych.data.get().csv()})); } function redirectToNextPage(redirect_html) { if((window.location.href).indexOf('?') != -1) { var variables = window.location.href.split('?')[1]; redirect_html += "?" + variables; }; last_trial_data = jsPsych.data.getLastTrialData().trials[0]; if(last_trial_data["chain"] != "false"){ window.location = redirect_html; }; }
原因分析
这个错误的核心是页面卸载时机与XHR请求收尾处理存在竞争:即使XHR触发了DONE状态并返回200,Firefox可能还未完成请求连接清理、响应数据完全接收等收尾操作,此时调用window.location会立即触发页面卸载,浏览器为释放资源会中止未完全收尾的XHR连接,从而抛出NS_BINDING_ABORTED错误。
注意:此时服务器通常已成功接收并保存数据,该错误是客户端层面的连接中止,不影响数据存储,但会干扰错误日志。
解决方案
方案1:使用navigator.sendBeacon()替代XHR(推荐)
navigator.sendBeacon()是浏览器专为页面卸载前发送数据设计的API,它会将请求加入后台发送队列,确保数据发送完成后再允许页面卸载,不会被页面操作中断。修改后的代码如下:
saveData(save_url, data_dir, file_name, extension, redirect_html, redirectToNextPage); function saveData(save_url, data_dir, file_name, extension, redirect_html, callback) { // 构造FormData(sendBeacon默认支持,无需手动设置Content-Type) const formData = new FormData(); formData.append('file_name', file_name); formData.append('extension', extension); formData.append('data_dir', data_dir); formData.append('data', jsPsych.data.get().csv()); // 尝试使用sendBeacon发送 const isQueued = navigator.sendBeacon(save_url, formData); if (isQueued) { callback(redirect_html); } else { // 兼容不支持sendBeacon的浏览器,降级到XHR const xhr = new XMLHttpRequest(); xhr.open('POST', save_url); xhr.setRequestHeader('Content-Type', 'application/json'); xhr.onreadystatechange = function() { if (xhr.readyState === XMLHttpRequest.DONE) { if (xhr.status === 200) { callback(redirect_html); } else { console.error('Error saving data: ' + xhr.status); } } }; xhr.send(JSON.stringify({ file_name: file_name, extension: extension, data_dir: data_dir, data: jsPsych.data.get().csv() })); } } // 重定向函数保持逻辑不变,优化变量声明 function redirectToNextPage(redirect_html) { if((window.location.href).indexOf('?') !== -1) { const variables = window.location.href.split('?')[1]; redirect_html += "?" + variables; } const last_trial_data = jsPsych.data.getLastTrialData().trials[0]; if(last_trial_data["chain"] !== "false"){ window.location = redirect_html; } }
注意:需修改后端write_data.php的接收逻辑,改用$_POST['file_name']等方式获取参数,而非json_decode(file_get_contents('php://input'))。
方案2:给重定向添加微小延迟
若不想修改后端逻辑,可在重定向前添加短暂延迟,给Firefox足够时间完成XHR请求收尾:
function redirectToNextPage(redirect_html) { if((window.location.href).indexOf('?') !== -1) { const variables = window.location.href.split('?')[1]; redirect_html += "?" + variables; } const last_trial_data = jsPsych.data.getLastTrialData().trials[0]; if(last_trial_data["chain"] !== "false"){ // 添加100ms延迟,给浏览器时间完成XHR收尾 setTimeout(() => { window.location = redirect_html; }, 100); } }
缺点:延迟时间需根据实际情况调整,无法保证100%兼容所有场景,但实现简单无需修改后端。
方案3:确保后端响应完全发送
在后端write_data.php中,关闭输出缓冲并强制刷新响应,确保客户端收到完整响应后再触发重定向:
<?php // 关闭输出缓冲 ob_end_clean(); // 处理数据保存逻辑... // 发送响应并立即刷新输出 echo json_encode(['status' => 'success']); ob_flush(); flush(); // 立即结束脚本 exit; ?>
内容的提问来源于stack exchange,提问作者RomainP

