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; }
问题排查与修复方案
核心问题分析
setTimeout未Promise化:原生setTimeout不返回Promise,直接使用await setTimeout无法等待延迟执行完成,导致Promise链提前结束。getFileResponse未返回完整Promise链:该方法中fetch的then回调逻辑没有被返回,使得await this.getFileResponse无法等待后续的状态查询和下载流程。- 递归分支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
相关产品推荐
相关产品推荐

