Node.js Worker线程中等待音频下载完成后执行裁剪的方法
问题解决:Worker线程中音频下载完成后再执行裁剪
问题背景
现有Node.js应用通过Worker线程从API下载音频,希望在同一Worker线程内完成音频下载后立即执行裁剪操作,但当前裁剪代码会在下载完成前并行运行,导致生成空文件。
原代码
主进程代码
// 连接API async function connectAPI() { await forLogin(); } // 获取数据后创建Worker线程 async function startWorkers() { return new Promise((resolve, reject) => { // 存储数据的数组(原代码此处省略部分逻辑) threads.add(new Worker("worker.js", { workerData: { worker: id, data: dataperWorker[id] } })); for (let worker of threads) { worker.on("error", () => { // 错误处理(原代码此处省略逻辑) }); worker.on("exit", () => { threads.delete(worker); return resolve; // 此处存在错误 }); worker.on("message", (message) => { // 接收Worker线程消息 switch (message.type) { case "log": break; } }); } }); }
Worker线程代码(worker.js)
async function downloadAudio(workerId, data, storagePath) { await forGetRecordings(); const file = fs.createWriteStream(`${storagePath}/fileName.mp3`); https.get(recordingsURL, async (response) => { response.pipe(file); }); } // 新增的音频裁剪函数 function splitAudio(data, storagePath) { return new Promise((resolve, reject) => { for (const value of data.values) { // 裁剪条件判断(原代码此处省略逻辑) ffmpeg(`${storagePath}/fileName.mp3`) .inputOptions([`-ss 40`, `-to 150`]) .output(`${storagePath}/fileName_id.mp3`) .on("end", function () { logger.info(`Trimming records completed!`); resolve(); }) .on("error", function (err) { logger.error(`Error occured ${err.message}`); reject(); }) .run(); } }); } async function init(workerId, data) { await forLogin(); // 创建本地文件夹用于下载音频(原代码此处省略逻辑) for (const i = 0; i < data.length; i++) { await downloadAudio(workerId, data[i], storagePath); // 下载完整音频 await splitAudio(data[i], storagePath); // 执行裁剪 } } init(workerData.worker, workerData.data);
核心问题分析
- downloadAudio未等待下载完成:
downloadAudio是async函数,但内部https.get和response.pipe(file)的异步操作没有被Promise包裹,导致await downloadAudio会直接跳过等待,立即执行后续的splitAudio,此时音频文件还未下载完成,裁剪时读取的是空文件。 - splitAudio的Promise处理错误:如果
data.values有多个元素,循环内会多次调用ffmpeg,但Promise只会在第一个ffmpeg完成时resolve或reject,后续的裁剪任务结果不会被捕获。 - startWorkers中的resolve调用错误:原代码中
return resolve;未实际调用resolve方法,导致Promise永远不会被决议。
修复方案
1. 修复downloadAudio函数,等待下载完成
将https.get和文件流的操作包裹成Promise,确保下载完成后才resolve:
async function downloadAudio(workerId, data, storagePath) { await forGetRecordings(); return new Promise((resolve, reject) => { const filePath = `${storagePath}/fileName.mp3`; const file = fs.createWriteStream(filePath); https.get(recordingsURL, (response) => { response.pipe(file); // 监听文件流完成事件 file.on('finish', () => { file.close(); // 关闭文件流 resolve(); }); // 监听文件流错误 file.on('error', (err) => { fs.unlink(filePath, () => {}); // 删除不完整的文件 reject(err); }); }).on('error', (err) => { reject(err); }); }); }
2. 修复splitAudio函数,处理多个裁剪任务
用Promise.all等待所有ffmpeg任务完成后再resolve,同时确保输出文件名唯一:
async function splitAudio(data, storagePath) { const trimPromises = []; for (const value of data.values) { // 裁剪条件判断(原代码此处省略逻辑) const outputPath = `${storagePath}/fileName_${value.id}.mp3`; // 避免文件名冲突 const trimPromise = new Promise((resolve, reject) => { ffmpeg(`${storagePath}/fileName.mp3`) .inputOptions([`-ss 40`, `-to 150`]) .output(outputPath) .on("end", () => { logger.info(`Trimming completed for ${outputPath}`); resolve(); }) .on("error", (err) => { logger.error(`Error trimming ${outputPath}: ${err.message}`); reject(err); }) .run(); }); trimPromises.push(trimPromise); } // 等待所有裁剪任务完成 await Promise.all(trimPromises); }
3. 修复startWorkers中的resolve调用
将return resolve;改为resolve();,确保Promise能正确决议:
worker.on("exit", () => { threads.delete(worker); resolve(); // 正确调用resolve方法 });
修复后的执行流程
修改后,init函数中的await downloadAudio会等待音频完全下载到本地文件后,才会执行await splitAudio,确保裁剪时读取的是完整的音频文件;同时splitAudio会等待所有裁剪任务完成后才继续下一轮循环。
内容的提问来源于stack exchange,提问作者dragon89
相关产品推荐
相关产品推荐

