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

DeepL文档翻译递归API调用未等待完成问题排查

问题描述

我正在使用DeepL API进行文档翻译,提交待翻译文件调用API后,可通过特定端点查询翻译状态,当状态为"done"时即可下载翻译后的文件。

我已实现递归调用该状态查询端点,在状态为完成时触发下载,但现在需要在翻译过程中显示加载弹窗。我已在首次调用前将showLoaderPopup状态设为true,但需等待所有操作完成后再将其设为false。目前的问题是TranslateFile方法在文件完成下载前就已返回,推测是Promise配置存在问题,请求帮忙排查,确保TranslateFile方法在所有操作完成后再返回,从而正确控制加载弹窗的显示与隐藏。

原代码

async TranslateFile(files: FileList, inputLang: string, outputLang: string){
    const formData = new FormData();
    if(inputLang) formData.append('source_lang', inputLang);
    formData.append('target_lang', outputLang);
    formData.append('file', files.item(0), files.item(0).name);
    const documentType = files.item(0).type;

    const requestOptions = this.getRequestFileOpt(formData);

    return await this.getFileResponse(requestOptions, documentType);
}

// FUNCTION THAT SEND THE FILE TO TRANSLATE AND CALL RECURSIVE FUNC FOR THE FIRST TIME
async getFileResponse(requestOptions: RequestInit, documentType: string) {
    const myHeaders = new Headers();
    myHeaders.append("Content-Type", "application/x-www-form-urlencoded");
    await fetch(`https://api.deepl.com/v2/document?auth_key=${apiKey}`, requestOptions)
        .then(result => result.json())
        .then(async (document: any) => {
            await setTimeout(async() => {
                return await this.checkDocumentTranslationState(document, documentType);
            }, 2000);
        })
}

// RECURSIVE FUNCTION TO CHECK TRANSLATION STATE
async checkDocumentTranslationState(document: any, documentType: string): Promise<boolean> {
    const myHeaders = new Headers();
    return await fetch(`https://api.deepl.com/v2/document/${document.document_id}?auth_key=${apiKey}&document_key=${document.document_key}`,{method: 'POST', headers: myHeaders})
        .then(result => result.json())    
        .then(async (documentState: any) => {
            if(documentState.status === "done") 
                return this.downloadToClient(await fetch(`https://api.deepl.com/v2/document/${document.document_id}/result?auth_key=${apiKey}&document_key=${document.document_key}`,{method: 'POST', headers: myHeaders}), documentType);
            else if(documentState.status === "error") return false;    
            else {
                let seconds_remaining = Number(documentState.seconds_remaining);
                setTimeout(async () => {
                    return await this.checkDocumentTranslationState(document, documentType);
                }, (seconds_remaining && !Number.isNaN(seconds_remaining) && (seconds_remaining < 5000)) ? (seconds_remaining) : 2000);
            }
        });  
}

// FUNCTION TO DOWNLOAD FILE
async downloadToClient(response: any, documentType: string): Promise<boolean> {
    const blob = await response.blob();
    const url = window.URL.createObjectURL(blob);
    const link = document.createElement('a');
    link.href = url;
    let fileName = "TranslatedFile";
    if(documentType === "application/vnd.openxmlformats-officedocument.wordprocessingml.document") fileName += ".docx";
    else if(documentType === "application/vnd.openxmlformats-officedocument.presentationml.presentation") fileName += ".pptx";
    else if(documentType === "application/pdf") fileName += ".pdf";
    else fileName += ".txt";
    link.setAttribute('download', fileName);
    document.body.appendChild(link);
    link.click();
    return true;
}
问题排查与修复方案

核心问题分析

  1. setTimeout未Promise化:原生setTimeout不返回Promise,直接使用await setTimeout无法等待延迟执行完成,导致Promise链提前结束。
  2. getFileResponse未返回完整Promise链:该方法中fetch的then回调逻辑没有被返回,使得await this.getFileResponse无法等待后续的状态查询和下载流程。
  3. 递归分支Promise链断裂:checkDocumentTranslationState中递归调用时,setTimeout包裹的递归逻辑没有被包装成Promise,导致方法提前resolve,无法等待后续递归完成。

修复后的代码

// 封装Promise版的setTimeout
private delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
}

async TranslateFile(files: FileList, inputLang: string, outputLang: string){
    const formData = new FormData();
    if(inputLang) formData.append('source_lang', inputLang);
    formData.append('target_lang', outputLang);
    formData.append('file', files.item(0), files.item(0).name);
    const documentType = files.item(0).type;

    const requestOptions = this.getRequestFileOpt(formData);

    return await this.getFileResponse(requestOptions, documentType);
}

async getFileResponse(requestOptions: RequestInit, documentType: string) {
    const myHeaders = new Headers();
    myHeaders.append("Content-Type", "application/x-www-form-urlencoded");
    // 返回完整的Promise链,确保await能等待后续流程
    const document = await fetch(`https://api.deepl.com/v2/document?auth_key=${apiKey}`, requestOptions)
        .then(result => result.json());
    
    // 使用封装的delay替代原生setTimeout,确保等待延迟完成
    await this.delay(2000);
    return this.checkDocumentTranslationState(document, documentType);
}

async checkDocumentTranslationState(document: any, documentType: string): Promise<boolean> {
    const myHeaders = new Headers();
    const documentState = await fetch(
        `https://api.deepl.com/v2/document/${document.document_id}?auth_key=${apiKey}&document_key=${document.document_key}`,
        {method: 'POST', headers: myHeaders}
    ).then(result => result.json());    

    if(documentState.status === "done") {
        const response = await fetch(
            `https://api.deepl.com/v2/document/${document.document_id}/result?auth_key=${apiKey}&document_key=${document.document_key}`,
            {method: 'POST', headers: myHeaders}
        );
        return this.downloadToClient(response, documentType);
    } else if(documentState.status === "error") {
        return false;    
    } else {
        let seconds_remaining = Number(documentState.seconds_remaining);
        const delayMs = (seconds_remaining && !Number.isNaN(seconds_remaining) && seconds_remaining < 5000) ? seconds_remaining : 2000;
        // 使用封装的delay,等待后再递归调用,返回Promise确保链不中断
        await this.delay(delayMs);
        return this.checkDocumentTranslationState(document, documentType);
    }
}

async downloadToClient(response: any, documentType: string): Promise<boolean> {
    const blob = await response.blob();
    const url = window.URL.createObjectURL(blob);
    const link = document.createElement('a');
    link.href = url;
    let fileName = "TranslatedFile";
    if(documentType === "application/vnd.openxmlformats-officedocument.wordprocessingml.document") fileName += ".docx";
    else if(documentType === "application/vnd.openxmlformats-officedocument.presentationml.presentation") fileName += ".pptx";
    else if(documentType === "application/pdf") fileName += ".pdf";
    else fileName += ".txt";
    link.setAttribute('download', fileName);
    document.body.appendChild(link);
    link.click();
    // 释放URL对象,避免内存泄漏
    window.URL.revokeObjectURL(url);
    return true;
}

关键修改说明

  • 新增delay方法封装setTimeout为Promise,确保所有延迟操作能被await正确等待。
  • getFileResponse改为使用await替代then链式调用,明确返回后续状态查询的Promise,保证整个流程的等待。
  • checkDocumentTranslationState中递归分支使用await this.delay替代原生setTimeout,并返回递归调用的Promise,确保Promise链完整。
  • 新增window.URL.revokeObjectURL(url)释放下载后的URL对象,避免内存泄漏。

内容的提问来源于stack exchange,提问作者Emili Bellot Pulido

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 09:07:13