You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

页面重载时XMLHttpRequest为何提前终止后台Celery翻译任务?

解决页面卸载时提前终止Celery翻译任务的问题

问题场景

用户启动后台翻译任务(Celery+Redis处理),期望:

  • 触发页面重载/跳转时,先弹出确认提示告知离开会终止任务
  • 仅在用户确认离开后,才调用后端stop_task接口终止任务

但当前实现中,弹窗刚出现,翻译任务就被直接终止,未等待用户确认。

问题原因

  1. beforeunload事件的误解:该事件仅用于触发浏览器原生确认弹窗,无法捕获用户的确认操作。你现在在beforeunload中直接调用stopTranslation(),导致无论用户选择「取消」还是「确认」,终止请求都会立即发送。
  2. 函数拼写错误:stopTranslaion少了一个t,实际调用时可能出现未定义错误(虽非当前核心问题,但需修正)。
  3. 异步请求可靠性问题:页面卸载时,普通XMLHttpRequest可能被浏览器中断,无法保证请求送达后端。

解决方案

  1. 拆分事件逻辑:
    • beforeunload仅负责触发确认弹窗,不执行任何终止操作
    • 监听unload(或pagehide,兼容性更佳)事件,该事件在用户确认离开、页面即将卸载时触发,此时执行终止逻辑
  2. 使用可靠的请求方式:用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 04:19:54