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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 07:54:03