如何在异步函数中循环遍历 Cherrio 并填充外部变量?

How to loop through a Cherrio inside an async function and populate an outside variable?

我需要创建一个 API 网络废料 GitHub 的回购获取以下数据:

  1. 文件名;
  2. 文件扩展名;
  3. 文件大小(字节、千字节、兆字节等);
  4. 文件行数;

我将 Node 与 TypeScript 一起使用,因此,为了充分利用它,我决定创建一个名为 FileInterface 的接口,它具有上述四个属性。

当然,变量是该接口的数组:

let files: FileInterface[] = [];

以我自己的repo为例:https://github.com/raphaelalvarenga/git-hub-web-scraping

到目前为止一切顺利。

我已经指向具有 request-promise 依赖项的 HTML 的文件部分,并将它们存储在 Cheerio 变量中,这样我就可以遍历 "tr" 标签来创建一个环形。正如您所想,那些 "tr" 标签代表 "table" 标签内的每个 files/folders(如果您检查页面,很容易找到它)。该循环将填充一个名为:

的临时变量
let tempFile: FileInterface;

并且在循环的每个循环结束时,数组将被填充:

files.push(tempFile);

在GitHub repo 的初始页面中,我们可以找到文件名及其扩展名。但是行的大小和总数,我们不能。单击它们以重定向到文件页面时会发现它们。假设我们点击了 README.md:

好的,现在我们可以看到 README.md 有 2 行和 91 字节。

我的问题是,因为这需要很长时间,所以它需要是一个异步函数。但是我无法处理异步函数中 Cheerio 内容中的循环。

我尝试过的事情:

  1. 使用map和each方法循环遍历并压入数组文件;
  2. 在循环之前使用等待。我知道这个实际上行不通,因为它只是一个没有 return 任何东西的循环;
  3. 我最后尝试并认为可行的是 Promise。但是 TypeScript 指责 Promises return "Promise unknown" 类型并且我不允许在文件数组中填充结果,因为类型 "unknown" 和 "FilesInterface[]" 不相等。

下面我将放置到目前为止我创建的代码。如果你想下载和测试,我会上传 repo(link 在这个 post 的开头),但我需要警告这个代码在分支 "repo-request-bad-loop".它不在主人身上。不要忘记,因为 master 分支没有我提到的任何东西 =)

我正在 Insomnia 中向路由“/”发出请求并传递这个 object:

{
   "action": "getRepoData",
   "url": "https://github.com/raphaelalvarenga/git-hub-web-scraping"
}

index-controller.ts 文件:

如您所见,它调用了有问题的 getRowData 文件。就在这里。

getRowData.ts 文件:

我会尽力帮助你,虽然我不会打字稿。我稍微重新修改了 getRowData 函数,现在它对我有用了:

import cheerio from "cheerio";
import FileInterface from "../interfaces/file-interface";
import getFileRemainingData from "../routines/getFileRemaningData";

const getRowData = async (html: string): Promise<FileInterface[]> => {

    const $ = cheerio.load(html);    

    const promises: any[] = $('.files .js-navigation-item').map(async (i: number, item: CheerioElement) => {
        const tempFile: FileInterface = {name: "", extension: "", size: "", totalLines: ""};
        const svgClasses = $(item).find(".icon > svg").attr("class");
        const isFile = svgClasses?.split(" ")[1] === "octicon-file";

        if (isFile) {
            // Get the file name
            const content: Cheerio = $(item).find("td.content a");
            tempFile.name = content.text();

            // Get the extension. In case the name is such as ".gitignore", the whole name will be considered
            const [filename, extension] = tempFile.name.split(".");
            tempFile.extension = filename === "" ? tempFile.name : extension;

            // Get the total lines and the size. A new request to the file screen will be needed
            const relativeLink = content.attr("href")
            const FILEURL = `https://github.com${relativeLink}`;

            const fileRemainingData: {totalLines: string, size: string} = await getFileRemainingData(FILEURL, tempFile);

            tempFile.totalLines = fileRemainingData.totalLines;
            tempFile.size = fileRemainingData.size;
        } else {
            // is not file
        }

        return tempFile;
    }).get();

    const files: FileInterface[] = await Promise.all(promises);

    return files;
}

export default getRowData;