【问题标题】:How to deal with a loop of promises dependant on another promise如何处理依赖于另一个承诺的承诺循环
【发布时间】:2019-12-07 04:00:39
【问题描述】:

我是 JavaScript 新手,但在使用 Promise 时遇到了麻烦。 我正在使用 cloudcraper 来检索网页的 html 以从中抓取数据。我有一个简单的函数 - getData() - 它调用 cloudcraper.get() 并将 html 传递给 extract() 函数,该函数负责抓取数据。 这是工作代码:

const getData = function(pageUrl) {
  var data;
  return cloudscraper.get(pageUrl)
    .then(function(html) {
      data = extract(html);
      return data;  
    })
    .catch(function(err) {
      // handle error
    })
}

返回的“数据”对象包含我要连接的 URL 数组,以便检索其他信息。该信息必须存储在同一个数据对象中。所以我想为数组中包含的每个 URL 再次调用 cloudcraper.get() 方法。 我试过下面的代码:

const getData = function(pageUrl) {
  var data;
  // first cloudscraper call:
  // retrieve main html
  return cloudscraper.get(pageUrl)
    .then(function(html) {
      // scrape data from it
      data = extract(html);
      for (let i = 0; i < data.array.length; ++i) {
        // for each URL scraped, call cloudscraper
        // to retrieve other data
        return cloudscraper.get(data.array[i])
          .then(function(newHtml) {
            // get other data with cheerio
            // and stores it in the same array
            data.array[i] = getNewData(newHtml);
          })
          .catch(function(err) {
            // handle error
          }) 
        }
        return data;  
      })
    .catch(function(err) {
      // handle error
    })
}

但它不起作用,因为数据对象是在循环中的承诺被解决之前返回的。 我知道可能有一个简单的解决方案,但我无法弄清楚,所以你能帮我吗?提前致谢。

【问题讨论】:

  • 你在循环的中间returning,这是行不通的。将多个 Promise 收集到一个数组中并使用 Promise.all
  • 如果要使用for循环,最好使用async/await,这样可以避免.then的promise链。
  • 感谢大家的快速回复。你能给我举个例子吗?我阅读了答案,但我不知道如何根据我的情况调整它们。

标签: javascript node.js promise


【解决方案1】:

避免此类问题的最佳方法是使用 async/await,如 cmets 中所建议的那样。这是基于您的代码的示例:

const getData = async function(pageUrl) {
  var data;
  // first cloudscraper call:
  // retrieve main html
  try {
    const html = await cloudscraper.get(pageUrl);
    // scrape data from it
    data = extract(html);
    for (let i = 0; i < data.array.length; ++i) {
      // for each URL scraped, call cloudscraper
      // to retrieve other data
      const newHtml = await cloudscraper.get(data.array[i]);
      // get other data with cheerio
      // and stores it in the same array
      data.array[i] = getNewData(newHtml); // if getNewData is also async, you need to add await
    }
  } catch (error) {
    // handle error
  }
  return data;
}
// You can call getData with .then().catch() outside of async functions 
// and with await inside async functions

【讨论】:

    【解决方案2】:

    这可以通过使用Promise.allawait/async来显着简化

    如果我的理解是正确的,您正在尝试执行以下步骤:

    1. 获取原始 HTML
    2. 提取一些 HTML(看起来你在寻找更多的 url)
    3. 对于每个提取的url,你想重新调用cloudscraper
    4. 将每次调用的结果放回原始数据对象中。

    const getData = async (pageUrl) => {
        const html = await cloudscraper.get(pageUrl);
        const data = extractHtml(html);
        const promises = data.array.map( d => cloudscraper.get(d));
        const results = await Promise.all(promises);
        // If you wanted to map the results back into the originaly data object
        data.array.forEach( (a, idx) => a = results[idx] );
        return data;
    };

    【讨论】:

    • 谢谢。代码不能开箱即用,因为我没有提到(为了简单起见)array 实际上是一个对象数组,每个对象都有一个url 属性,而不是直接的 URL 数组。所以我想我必须将内部cloudscraper调用更改为cloudscraper.get(d.url)
    • 是的,您必须在问题中指定这一点才能让我知道。但是,是的,正如您所了解的,您可以执行d.url(或您想要的任何属性)。如果这有帮助,请标记为答案或投票。
    • 它不起作用,也许我也必须在 forEach 语句中更改一些内容,不是吗?
    • 是的,url 字段不会更新。从技术上讲,这不是错误,很难。
    • 啊。我看到一个错字。修复。 promises[idx] 应该是 results[idx]。请记得点赞
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-11-11
    • 2014-01-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-13
    • 2019-09-23
    相关资源
    最近更新 更多