简体   繁体   English

我在等待 Nodejs 中的多个异步函数时遇到问题

[英]I'm having issues with awaiting multiple async functions in Nodejs

I have a function that uses Axios to download a zip file and extract the file into a temporary directory.我有一个使用 Axios 下载 zip 文件并将文件解压缩到临时目录的函数。 The process itself works as intended, but I'm having difficulty awaiting the final result before proceeding.该过程本身按预期工作,但在继续之前我很难等待最终结果。 I'll admit that I don't fully understand how to use promises, but that's what I need help learning.我承认我并不完全理解如何使用 promise,但这正是我需要帮助学习的。

Here is the complete code:这是完整的代码:

const axios = require('axios');
const StreamZip = require('node-stream-zip');

// Pipedream: steps.trigger.raw_event.body.result_set.download_links.json.all_pages
// Testing: https://api.countdownapi.com/download/results/04_NOVEMBER_2021/1900/Collection_Results_F4C0B671_51_All_Pages.zip
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() {
    return await axios.get(all_pages, {responseType: "stream"})
        .then(res => {
            console.log("Waiting ...")

            if (res.status === 200) {
                const path = require("path");
                const SUB_FOLDER = "";
                fileName = fileName || all_pages.split("/").pop();

                const dir = path.resolve(__dirname, SUB_FOLDER, fileName);
                res.data.pipe(fs.createWriteStream(dir));
                res.data.on("end", () => {
                    console.log("Download Completed");

                    const zip = new StreamZip({
                        file: dir,
                        storeEntries: true
                    });
                    zip.on('error', function (err) {
                        console.error('[ERROR]', err);
                    });
                    zip.on('ready', function () {
                        console.log('All entries read: ' + zip.entriesCount);
                        // console.log(zip.entries());
                    });
                    zip.on('entry', function (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;
                        }

                        console.log('[FILE]', entry.name);
                        zip.stream(entry.name, function (err, stream) {
                            if (err) {
                                console.error('Error:', err.toString());
                                return;
                            }

                            stream.on('error', function (err) {
                                console.log('[ERROR]', err);
                            });

                            // example: print contents to screen
                            // stream.pipe(process.stdout);

                            // example: save contents to file
                            fs.mkdir(path.dirname(pathname), {recursive: true}, function () {
                                    stream.pipe(fs.createWriteStream(pathname));
                                }
                            );
                        });
                    });
                });
            } else {
                console.log(`ERROR >> ${res.status}`);
            }
        })
        .catch(err => {
            console.log("Error ", err);
        });
}

(async () => {
    try {
        await asyncFunc();
        console.log('Finished')
    } catch (error) {
        console.error(error);
    }
})();

As I said, the code itself works in that it'll download the zip file and extract the contents—however, my test console.log('Finished') fires just after the Axios get.正如我所说,代码本身的工作原理是它会下载 zip 文件并提取内容——但是,我的测试console.log('Finished')在 Axios 获取后立即触发。 Here are the results of the order of operations:以下是操作顺序的结果:

Waiting ...
Finished
Download Completed
[FILE] Collection_Results_F4C0B671_51_Page_1.json
[FILE] Collection_Results_F4C0B671_51_Page_2.json
[FILE] Collection_Results_F4C0B671_51_Page_3.json
[FILE] Collection_Results_F4C0B671_51_Page_4.json
[FILE] Collection_Results_F4C0B671_51_Page_5.json
[FILE] Collection_Results_F4C0B671_51_Page_6.json
[FILE] Collection_Results_F4C0B671_51_Page_7.json
All entries read: 7

I've tried reading other articles on Promises and similar questions, and I've tried many options without any luck.我尝试阅读有关 Promises 和类似问题的其他文章,但我尝试了很多选择,但都没有成功。

A major advantage of using Async/Await is that you can avoid deeply nested, difficult to read code - such as yours.使用 Async/Await 的一个主要优点是您可以避免深度嵌套、难以阅读的代码 - 例如您的代码。 It makes much more sense to break this code into functional units.将此代码分解为功能单元更有意义。 Rather than thinking about all this code as "must be together", think "works better when apart".与其认为所有这些代码“必须在一起”,不如认为“分开时效果更好”。

So the entry point can call axios, use .then() to fire off the data file download, use .then() to fire off unzipping, use then() to fire off stream writing function.因此入口点可以调用 axios,使用 .then .then()触发数据文件下载,使用.then()触发解压缩,使用then()触发流写入功能。

You have created a dilemma by using the callback version of StreamZip .通过使用StreamZip的回调版本,您陷入了困境。 It would simplify things a lot if you used the Promise version the API.如果您使用 Promise 版本的 API,它会简化很多事情。

Something like the following is easier to rationalize about the order of operation.像下面这样的东西更容易使操作顺序合理化。

  try {
    console.log('Starting')
    axios.get(all_pages, {responseType: "stream"})
      .then(download)
      .then(unzip)
      .then(writeFile)
    console.log('Finished')
  } catch (error) {
    console.error(error);
  }

If you want the Finished statement to show up after all the entries are read, why not just add it to this section of the code?如果您希望在读取所有条目后显示 Finished 语句,为什么不将其添加到代码的这一部分?

                    zip.on('ready', function () {
                        console.log('All entries read: ' + zip.entriesCount);
                        // console.log(zip.entries());
                        // ADD THE FINISHED STATEMENT HERE
                    });

Edit编辑

Base on the docs you can do the following after the end of the stream.根据文档,您可以在流结束后执行以下操作。

const stm = await zip.stream('path/inside/zip.txt');
stm.on('end', () => {
    zip.close();
    // FINISHED AT THIS POINT ?

})

This is another place where you can say you are done streaming (Finished).这是您可以说您已完成流式传输(已完成)的另一个地方。 Depending on the usage you may not have to close the zip here.根据使用情况,您可能不必在此处关闭拉链。

You can simply return a new promise instead of the one returned by axios.get and resolve when download is finished like so:您可以简单地返回一个新的承诺而不是axios.get返回的axios.get并在下载完成时解决,如下所示:

async function asyncFunc() {
  return new Promise(function(resolve, reject){
    axios.get(all_pages, {responseType: "stream"})
      .then(res => {
          console.log("Waiting ...")
          //...
          res.data.on("end", () => {
              console.log("Download Completed");
              // resolve the promise when download is finished
              resolve();

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM