断开网络导致AngularJS(Ionic v1)进程中断问题排查
解决AngularJS(Ionic v1)批量上传中途断网时请求"僵尸状态"问题
这个问题的核心是:当网络中途断开时,部分$http请求会陷入pending状态(僵尸状态),既不进入成功回调也不进入失败回调,导致后续的数据库更新逻辑完全无法执行——哪怕照片已经成功传到了服务器。我来拆解原因并给出具体的解决方案:
为什么会出现"僵尸状态"?
浏览器在网络中断时,会默认挂起未完成的HTTP请求,等待网络恢复后重试。而AngularJS的$http服务本身没有默认超时机制,这就导致这些请求会一直处于pending状态,永远不会触发then或catch,你写的数据库更新代码自然就被卡住了。
另外你提到的online/offline监听器如果没有处理pending请求的逻辑,反而可能加剧这个问题——因为网络状态切换时,这些挂起的请求不会被主动终止。
解决方案1:给$http请求添加强制超时
最直接的办法是给每个上传请求设置超时时间,确保即使网络断了,请求也能在指定时间后进入catch回调,让你有机会处理后续逻辑。
修改你的sendPhotoInHttpRequest函数:
function sendPhotoInHttpRequest(payload) { // 定义5秒超时(可根据业务调整) const timeoutPromise = $timeout(() => {}, 5000); return $http({ url: url, method: 'POST', data: JSON.stringify(payload), timeout: timeoutPromise // 绑定超时逻辑 }).then(res => { // 上传成功,正常更新数据库 return updateDatabaseSetPhotoAsSent(payload.id) .then(() => { increaseCounter(counterObj); return 'ok'; }); }).catch(err => { // 区分超时/网络错误和服务器异常 if (err.status === -1 || err.message.includes('timeout')) { // 这里可以根据实际情况判断:如果确认照片已到服务器,直接更新数据库 // (比如可以通过后续服务器接口校验,或者本地标记后同步) return updateDatabaseSetPhotoAsSent(payload.id) .then(() => { increaseCounter(counterObj); return 'ok (recovered from network issue)'; }); } // 其他错误继续抛出,不影响正常错误处理 throw err; }); }
解决方案2:监听网络状态,主动取消pending请求
结合你已经配置的online/offline监听器,我们可以主动管理所有pending的请求,在网络断开时直接取消它们,避免请求一直挂起。
- 先添加一个HTTP拦截器,用来追踪所有pending请求:
// 在app.js中定义拦截器 app.factory('pendingRequestInterceptor', function($q) { const pendingRequests = []; return { request: function(config) { // 给每个请求绑定一个取消器 config.cancelToken = $q.defer(); pendingRequests.push(config); return config; }, response: function(response) { // 请求完成后移除追踪 const index = pendingRequests.indexOf(response.config); if (index !== -1) pendingRequests.splice(index, 1); return response; }, responseError: function(rejection) { // 请求失败后移除追踪 const index = pendingRequests.indexOf(rejection.config); if (index !== -1) pendingRequests.splice(index, 1); return $q.reject(rejection); }, // 暴露取消所有pending请求的方法 cancelAll: function() { pendingRequests.forEach(req => req.cancelToken.resolve('Network offline')); pendingRequests.length = 0; } }; }); // 注册拦截器 app.config(function($httpProvider) { $httpProvider.interceptors.push('pendingRequestInterceptor'); });
- 在
offline事件中调用取消方法:
app.run(function($rootScope, pendingRequestInterceptor) { $rootScope.$on('$cordovaNetwork:offline', function() { // 网络断开时,取消所有pending请求 pendingRequestInterceptor.cancelAll(); }); });
- 最后修改上传函数,使用取消器:
function sendPhotoInHttpRequest(payload) { return $http({ url: url, method: 'POST', data: JSON.stringify(payload), timeout: config.cancelToken.promise }).then(res => { return updateDatabaseSetPhotoAsSent(payload.id) .then(() => { increaseCounter(counterObj); return 'ok'; }); }).catch(err => { if (err.message === 'Network offline') { // 处理网络断开的情况,根据业务更新数据库 return updateDatabaseSetPhotoAsSent(payload.id) .then(() => { increaseCounter(counterObj); return 'ok (canceled due to offline)'; }); } throw err; }); }
额外提醒:避免手动$q.defer反模式
你原来的代码里手动创建了defer对象,这其实是AngularJS中的反模式——$http本身已经返回Promise,直接链式调用then即可,不需要手动管理defer,这样代码更简洁也更不容易出错。
内容的提问来源于stack exchange,提问作者Edson Horacio Junior
相关产品推荐
相关产品推荐

