异步函数不返回解析到变量/常量

时间:2019-04-29 19:32:07

标签: javascript node.js

我一直致力于制作一个简单的Web抓取工具,以更深入地了解使用异步/等待和承诺/解决功能。我在使用async / await函数时遇到问题,无法正确返回解析。我可以在返回解析之前,在函数内console.log完整的数据集,但是我无法分配常量并在函数本身范围之外调用函数,而console.log该数据(它以未定义的形式返回)。

这是应该返回要抓取的数据集的函数。 saleHeaders已成功提取,因为我能够抓取内容并将其分配给它们各自的常量。使用下面注释掉的console.log时,我将获得完整的数据集输出。

async function getInnerPosts(saleHeaders) {
  await Promise.all(saleHeaders.map(async (job) => {
    const dataSet = [];
    return new Promise(resolve => {
      request(job.fullURL, (err, res, html) => {
        const $ = cheerio.load(html);
        $('.result-info').each((index, element) => {
            const postTitle     = $(element).children(".result-title").text();
            const postDate      = $(element).children(".result-date").attr('title');
            const postLink      = $(element).children("a").attr('href');
            const postPrice     = $(element).children(".result-meta").children(".result-price").text();
            const postLocation  = $(element).children(".result-meta").children(".result-hood").text().replace(/[{()}]/g, '');
            // gather data to one const
            const fetchedData   = { postTitle, postDate, postLink, postPrice, postLocation };
            dataSet.push(fetchedData);
            // console.log(dataSet);
        });
        return resolve(dataSet);
      });
    });
  }));
}

但是当我尝试运行该函数,将其分配给一个常量,然后尝试记录该常量时,我​​变得未定义,没有引发任何警告/错误。我试图重新构造数据集的返回结构,并从头开始重写完整的函数,以确保我不会错过一些小错误,但是没有运气。

async function scrapeData() {
    const saleHeaders = await getForSaleHeader();
    // Loop through the categories and pull the inner-data from the posts page
    const innerPosts = await getInnerPosts(saleHeaders);
    console.log(innerPosts);
}

这是时间,运行上述代码示例时我正在接收输出:

undefined

real    0m31.272s
user    0m33.594s
sys 0m0.322s

编辑 我正在调用整个脚本来运行:

// run the script
scrapeData();

3 个答案:

答案 0 :(得分:0)

async function getInnerPosts(saleHeaders) {
  // here
  return await Promise.all(saleHeaders.map(async (job) => {
    const dataSet = [];
    return new Promise(resolve => {

您必须返回它,因为您想进一步使用函数外的值。

const innerPosts = await getInnerPosts(saleHeaders);

如果不这样做,您认为innerPosts的值从哪里来?它是从getInnerPosts返回的!

答案 1 :(得分:0)

问题在于您的return语句仅在map函数内部返回(map具有return语句,但函数本身没有!

答案 2 :(得分:0)

问题的根源是您将Promises与async / await混合在一起。请记住,如果函数返回Promise(例如return new Promisereturn Promise.all),则不应async

一个较小但同样重要的问题是getInnerPosts不返回任何内容。它以await Promise.all(...)开头。它应以return Promise.all(...)开头。在没有return的情况下,getInnerPosts返回undefined,因此调用它的函数实际上只是在执行await undefined,它立即解析为undefined

让我们逐步解决这个问题。首先,通过将cheerio选择器代码拖入其自己的函数中,以消除大部分代码:

function getData(element) {
  const $el = $(element);
  return {
    postTitle: $el.children('.result-title').text(),
    postDate: $el.children('.result-date').attr('title'),
    postLink: $el.children('a').attr('href'),
    postPrice: $el.children('.result-meta > .result-price').text(),
    postLocation: $el.children('.result-meta > .result-hood').text().replace(/[{()}]/g, ''),
  };
}

接下来,摆脱async / await并在return之前添加Promise.all

function getInnerPosts(saleHeaders) {
  return Promise.all(saleHeaders.map(job => {
    const dataSet = [];
    return new Promise(resolve => {
      request(job.fullURL, (err, res, html) => {
        const $ = cheerio.load(html);
        $('.result-info').each((index, element) => dataSet.push(getData(element)));
        resolve(dataSet);
      });
    });
  }));
}

这应该足以使您的代码按预期工作。但是,这种金字塔结构不是很容易阅读。让我们看看是否可以改善它。

首先,您可以将对request的调用包装在一个返回Promise的函数中(或者,您可以使用request-promise-native)。完成此操作后,您可以使传递给saleHeaders.map的回调函数成为async函数,并在其中使用await pRequest(...)。最后,您可以通过使用cheerio的dataSet而不是在map循环内调用push来完全消除each。最终结果如下:

function pRequest(url) {
  return new Promise((resolve, reject) =>
    request(url, (err, res, html) => err ? reject(err) : resolve(html)));
}

function getInnerPosts(saleHeaders) {
  return Promise.all(saleHeaders.map(async job => {
    const html = await pRequest(job.fullURL);
    const $ = cheerio.load(html);
    return $('.result-info').map((index, element) => getData(element)));
  }));
}

回复您的评论:

  

仍然试图绕过async / await和promise之间的差异。

了解async / await与promise之间的差异(或更重要的是关系)并非微不足道。它需要三到四层的理解。您需要了解JS中的异步回调(例如,传递给requestsetTimeout的函数)如何工作,然后您需要了解Promise与回调的关系,最后是async / await与诺言有关。这是一个很难解决的难题,我认为大多数人只是通过经验来掌握它。

如果我有任何建议,请放慢速度。考虑一下代码的每个部分以及它的实际作用。当您编写此代码时:

request(url, (err, res, body) => { /* … */ });

request函数中的第二个参数(回调)会发生什么?

当您写这篇文章时:

fetch(url)
  .then(res => { /* … */ })
  .catch (err => { /* … */ });

fetch(url)返回什么,then是什么?为什么要改写这个(在async函数中)?

try {
  const res = await fetch(url);
  // …
} catch (err) {
  // …
}

它的行为是否相同,还是有区别?

放慢脚步,想想你在写什么。除非您知道为什么这么做,否则不要添加await。除非您知道自己为什么这么做,否则不要添加new Promise

最后,将您的代码分解为较小的函数。当函数具有returnawait和三个嵌套的回调函数时,很容易忘记需要Promise.all的位置或想要new Promise的位置。如果函数只有2–3行,那么判断一个函数是否可以为async就容易得多。

  

使用一个相对于另一个有什么优势吗?

IMO async / await的主要优点是可读性。 Promise构造函数和嵌套的then / catch回调 work ,但是它们往往使您的代码难以理解。 async / await使您的代码阅读起来更像普通的同步代码(更不用说“ await”是一个已经很熟悉的英语单词),这使您更容易理解意图。当然,您仍然需要Promises。 await使功能有效地暂停,但是有时在执行其余功能时只想延迟一件事,因此您需要thenPromise.all

不可替代