Node.js中await多个异步函数时执行顺序不符合预期问题求解
问题根源
你当前代码的问题核心是:axios.get返回的Promise只会在请求响应头接收完成后就resolve,后续的下载流写入、zip解压都是基于事件回调执行的逻辑,没有被纳入asyncFunc返回的Promise的等待范围,所以await会直接跳过这些后续流程,导致Finished提前打印。
解决方案就是把所有需要等待的异步流程手动封装为Promise,让asyncFunc返回的Promise在所有流程(下载完成、全部文件解压完成)执行完毕后再resolve。
修正后完整代码
const axios = require('axios'); const StreamZip = require('node-stream-zip'); const fs = require('fs'); const path = require('path'); // 测试下载地址 const all_pages = 'https://api.countdownapi.com/download/results/04_NOVEMBER_2021/1900/Collection_Results_F4C0B671_51_All_Pages.zip'; let fileName = 'all_pages.zip'; async function asyncFunc() { const res = await axios.get(all_pages, {responseType: "stream"}); console.log("Waiting ...") if (res.status !== 200) { throw new Error(`请求错误,状态码:${res.status}`); } // 1. 封装下载流写入完成的Promise const downloadPromise = new Promise((resolve, reject) => { const SUB_FOLDER = ""; fileName = fileName || all_pages.split("/").pop(); const dir = path.resolve(__dirname, SUB_FOLDER, fileName); const writeStream = fs.createWriteStream(dir); res.data.pipe(writeStream); res.data.on("end", () => { console.log("Download Completed"); resolve(dir); // 返回下载好的zip文件路径给后续流程 }); res.data.on("error", reject); writeStream.on("error", reject); }); const zipPath = await downloadPromise; // 2. 封装zip全部解压完成的Promise const unzipPromise = new Promise((resolve, reject) => { const zip = new StreamZip({ file: zipPath, storeEntries: true }); let totalFiles = 0; let finishedFiles = 0; zip.on('error', reject); zip.on('ready', () => { console.log('All entries read: ' + zip.entriesCount); // 当所有文件都处理完成再resolve if (totalFiles === finishedFiles) { zip.close(); resolve(); } }); zip.on('entry', (entry) => { const pathname = path.resolve('./tmp', entry.name); if (/\.\./.test(path.relative('./tmp', pathname))) { console.warn("[zip warn]: ignoring maliciously crafted paths in zip file:", entry.name); return; } if ('/' === entry.name[entry.name.length - 1]) { console.log('[DIR]', entry.name); return; } totalFiles++; console.log('[FILE]', entry.name); zip.stream(entry.name, (err, stream) => { if (err) return reject(err); stream.on('error', reject); // 封装文件写入完成的逻辑 fs.mkdir(path.dirname(pathname), {recursive: true}, () => { const fileWriteStream = fs.createWriteStream(pathname); stream.pipe(fileWriteStream); fileWriteStream.on('finish', () => { finishedFiles++; // 所有文件写入完成后触发resolve if (finishedFiles === totalFiles && zip.entriesCount) { zip.close(); resolve(); } }); fileWriteStream.on('error', reject); }); }); }); }); await unzipPromise; } (async () => { try { await asyncFunc(); console.log('Finished') } catch (error) { console.error("流程执行出错:", error); } })();
核心修改说明
- 补全了
fs、path依赖的引入,避免运行报错 - 把下载流写入、zip解压两个核心异步流程都包装为独立Promise,只有流程完全执行完成才会resolve
- 新增了流错误、写入错误的捕获逻辑,所有错误都会向外抛出,外层可以正常catch处理
- 增加了解压文件的计数逻辑,只有所有压缩包内的文件全部写入磁盘后,才会结束整个asyncFunc的等待
- 移除了冗余的then/catch嵌套,用更清晰的async/await语法拆分流程
内容的提问来源于stack exchange,提问作者Keith Petrillo
相关产品推荐
相关产品推荐

