如何等待API完成本地音频文件写入后再上传至S3存储桶?
解决ElevenLabs TTS生成后立即上传S3失败的问题
问题描述
使用elevenlabs-api生成TTS音频并保存到本地后,立即上传到S3存储桶时代码执行失败。原因是文件写入磁盘需要时间(文件越大耗时越久),直接后续读取上传会遇到文件未写完的情况。尝试用setTimeout临时解决,但效率低、不灵活,且API请求仍会超时,不是理想方案。虽已在TextToSpeech和fileContent前加了await,但未起作用,对Node/Express的异步Promise机制理解不足。
解决方案
1. 确保TextToSpeech函数返回Promise
如果TextToSpeech是基于回调的函数,必须将其包装为Promise,保证文件完全写入后才执行后续逻辑:
// 包装回调式的TextToSpeech为Promise function TextToSpeechPromise(apiKey, text, voice_id, fileurl) { return new Promise((resolve, reject) => { TextToSpeech(apiKey, text, voice_id, fileurl, (err, result) => { if (err) reject(err); else resolve(result); }); }); }
调用时使用await TextToSpeechPromise(...),确保文件写完后再执行上传步骤。
2. 移除fs.createReadStream前的await
fs.createReadStream是同步创建流的方法,不返回Promise,await对它无效,直接创建即可:
const fileContent = fs.createReadStream('C:\\Users\\User\\Projects\\backend\\public\\upload\\text_to_speech\\' + fileName);
3. 使用Promise化的S3上传方法
原代码用回调式的putObject无法通过await等待上传完成,改用Promise形式确保上传完成后再结束请求:
await s3.putObject({ Bucket: 'bucket', Key: `${fileName}`, Body: fileContent, ACL: 'public-read' }).promise();
完整修正代码
const fs = require('fs'); const aws = require('aws-sdk'); // 假设TextToSpeech是回调式函数,先包装为Promise function TextToSpeechPromise(apiKey, text, voice_id, fileurl) { return new Promise((resolve, reject) => { TextToSpeech(apiKey, text, voice_id, fileurl, (err, result) => { if (err) reject(err); else resolve(result); }); }); } exports.tts = async (req, res) => { try { // 等待音频文件完全写入本地 let audio = await TextToSpeechPromise(apiKey, text, voice_id, fileurl); console.log(`Success, Audio saved as: ${fileName}`); const spacesEndpoint = new aws.Endpoint('endpoint'); const s3 = new aws.S3({ endpoint: spacesEndpoint, accessKeyId: process.env.AWSACCESSKEYID, secretAccessKey: process.env.SECRETACCESSKEY, }); // 创建文件读取流 const fileContent = fs.createReadStream('C:\\Users\\User\\Projects\\backend\\public\\upload\\text_to_speech\\' + fileName); // 等待S3上传完成 await s3.putObject({ Bucket: 'bucket', Key: `${fileName}`, Body: fileContent, ACL: 'public-read' }).promise(); console.log('文件上传至S3成功'); res.status(200).json({ message: '音频生成并上传成功', fileName }); } catch (err) { console.error('操作失败:', err); res.status(500).json({ error: '音频生成或上传失败' }); } };
注意事项
- 必须为async函数添加
try/catch块,捕获异步操作中的错误,避免请求挂起或服务器崩溃。 - 确认
TextToSpeech函数的内部实现:如果它使用fs.writeFile等异步写入方法,必须等待写入完成才触发回调或resolve Promise,否则仍会出现文件未写完的问题。
内容的提问来源于stack exchange,提问作者Halnex
相关产品推荐
相关产品推荐

