页面重载时XMLHttpRequest为何提前终止后台Celery翻译任务?
解决页面卸载时提前终止Celery翻译任务的问题
问题场景
用户启动后台翻译任务(Celery+Redis处理),期望:
- 触发页面重载/跳转时,先弹出确认提示告知离开会终止任务
- 仅在用户确认离开后,才调用后端
stop_task接口终止任务
但当前实现中,弹窗刚出现,翻译任务就被直接终止,未等待用户确认。
问题原因
beforeunload事件的误解:该事件仅用于触发浏览器原生确认弹窗,无法捕获用户的确认操作。你现在在beforeunload中直接调用stopTranslation(),导致无论用户选择「取消」还是「确认」,终止请求都会立即发送。- 函数拼写错误:
stopTranslaion少了一个t,实际调用时可能出现未定义错误(虽非当前核心问题,但需修正)。 - 异步请求可靠性问题:页面卸载时,普通XMLHttpRequest可能被浏览器中断,无法保证请求送达后端。
解决方案
- 拆分事件逻辑:
beforeunload仅负责触发确认弹窗,不执行任何终止操作- 监听
unload(或pagehide,兼容性更佳)事件,该事件在用户确认离开、页面即将卸载时触发,此时执行终止逻辑
- 使用可靠的请求方式:用
navigator.sendBeacon()替代XMLHttpRequest,它专门为页面卸载时的异步请求设计,能保证请求被后台发送且不阻塞页面卸载。
修改后的代码
JavaScript代码
window.addEventListener('beforeunload', function (e) { if (isTranslating) { // 仅触发浏览器确认弹窗,不执行终止逻辑 e.preventDefault(); e.returnValue = 'Translation in progress. Are you sure you want to leave?'; return 'Translation in progress. Are you sure you want to leave?'; } }); // 用户确认离开后,页面卸载时执行终止逻辑 window.addEventListener('unload', function () { if (isTranslating && currentTaskId) { clearInterval(pollInterval); // 用sendBeacon发送请求,确保页面卸载时请求能成功送达 const formData = new FormData(); formData.append('csrfmiddlewaretoken', getCsrfToken()); navigator.sendBeacon(`/translate/stop_task/${currentTaskId}/`, formData); // 更新状态避免重复触发 isTranslating = false; currentTaskId = null; } }); // 修正函数名拼写错误 function stopTranslation() { if (isTranslating && currentTaskId) { clearInterval(pollInterval); const xhr = new XMLHttpRequest(); xhr.onload = function() { if (xhr.status == 200) { const response = JSON.parse(xhr.responseText); if (response.status === 'stopped') { console.log('Translation stopped'); isTranslating = false; currentTaskId = null; showTranslationStopped(); } } else { console.error('Failed to stop translation'); } }; xhr.onerror = function() { console.error('Connection error while trying to stop translation'); }; xhr.open('POST', `/translate/stop_task/${currentTaskId}/`, true); xhr.setRequestHeader('X-CSRFToken', getCsrfToken()); xhr.send(); } } function showTranslationStopped() { translatingFileField.style.display = 'none'; const stoppedMessage = document.createElement('div'); stoppedMessage.textContent = 'Translation has been stopped.'; form.appendChild(stoppedMessage); }
后端views.py(无需大幅修改)
from django.http import JsonResponse from celery import app @csrf_protect def stop_task(request, task_id): if request.method == 'POST': try: # 终止Celery任务 app.control.revoke(task_id, terminate=True) return JsonResponse({'status': 'stopped'}) except Exception as e: return JsonResponse({'status': 'error', 'message': str(e)}, status=500) return JsonResponse({'status': 'error', 'message': 'Invalid request method'}, status=400)
补充说明
navigator.sendBeacon会自动处理请求的异步发送,FormData格式能被Django正常解析,CSRF token可通过formData.append携带- 页面卸载时,
showTranslationStopped()这类UI更新已无意义,因此在unload事件中无需调用 - 若需兼容极旧浏览器,可降级为同步XMLHttpRequest(但会阻塞页面卸载,不推荐)
内容的提问来源于stack exchange,提问作者tthheemmaannii
相关产品推荐
相关产品推荐

