【问题标题】:Return from function that uses multiple async calls从使用多个异步调用的函数返回
【发布时间】:2018-10-29 02:05:05
【问题描述】:

因此,基本思想是编写一个方法,该方法将废弃网页以获取包含产品评级的 JSON 数据。然后在几个域(.de、.uk、.fr、.nl 等)上多次调用此方法以收集所有评级。

所以我最终使用了 scrapWebPage 方法,该方法会删除单个页面:

const scrapWebPage = async (countryAppData, productNumber) => {
    const shopUrl = `https://www.shopExample.${countryAppData.countryCode}/?q=${productNumber}`
    const avoidCORSUrl = 'https://allorigins.me/get?url=' + shopUrl + '&callback=?'

    return await axios
        .get(avoidCORSUrl, {xmlMode: false, normalizeWhitespace: true})
        .then(response => {
            const $ = cheerio.load(response.data)
            let scrapedWebPageJson

            contentForParsing = $("script").get().children[0].data                   
            scrapedWebPageJson = JSON.parse(contentForParsing)

            return scrapedWebPageJson
        })
}

scrapWebPage 还包含一些解析以返回我想要的一些 JSON 数据 - 它正确解析(经过测试)并返回 Promise。

但后来我想在多个域上调用此方法,所以我创建了getProductDataFromManyDomains

const getProductDataFromManyDomains = (productNum) => {
    let prodData = {
        reviews: []
    }

    const appCountries = [
        {countryCode: 'nl'}, 
        {countryCode: 'pl'},
        {countryCode: 'de'}
    ]

    appCountries.forEach(async countryApp => {
        let countryData = {}

        let parsedWebPage = await scrapWebPage(countryApp, productNum)

        countryData.countryCode  = countryApp.countryCode
        countryData.ratingCount  = parsedWebPage.aggregateRating.ratingCount
        countryData.ratingValue  = parsedWebPage.aggregateRating.ratingValue
        countryData.reviews      = parsedWebPage.reviews   

        prodData.reviews.push(countryData)
    })

    return prodData
}

现在我在填充之前收到prodData...而我想接收实际数据(填充prodData)。

我不确定我应该如何构造这个getProductDataFromManyDomains 方法来实际返回数据,而不是在填充之前prodData。那可能吗?或者这里有什么好的模式来处理这样的事情?

【问题讨论】:

  • 使用for 循环而不是.forEach()for 循环将暂停 await.forEach() 循环不会。然后,getProductDataFromManyDomains() 将需要是异步的,并将返回一个带有最终结果的承诺。
  • @jfriend00 哇?!直到,谢谢!
  • @jfriend00 我不太明白你...我可以做for (let countryApp of appCountries) {...}getProductDataFromManyDomains async 但是我应该在这个方法中返回什么呢?我猜不是return prodData,因为它不会被填充...我迷路了:/
  • @stackustack - 代码示例放入下面的答案中。
  • 使用同步请求从不同域拉取数据是个糟糕的主意。此类请求必须异步完成。

标签: javascript asynchronous promise async-await axios


【解决方案1】:

使用for 循环而不是.forEach()for 循环将暂停等待,.forEach() 循环不会。这是因为您传递给.forEach()async 回调将返回一个承诺,但.forEach() 并非旨在对该承诺做任何事情,因此它不会等待它解决后再继续循环,而是@987654328使用await 进行@循环。

然后,getProductDataFromManyDomains() 将需要为 async 并将返回一个带有最终结果的承诺。

async function getProductDataFromManyDomains(productNum) {
    let prodData = {
        reviews: []
    }

    const appCountries = [
        {countryCode: 'nl'}, 
        {countryCode: 'pl'},
        {countryCode: 'de'}
    ]

    for (let countryApp of appCountries) {
        let countryData = {}

        let parsedWebPage = await scrapWebPage(countryApp, productNum)

        countryData.countryCode  = countryApp.countryCode
        countryData.ratingCount  = parsedWebPage.aggregateRating.ratingCount
        countryData.ratingValue  = parsedWebPage.aggregateRating.ratingValue
        countryData.reviews      = parsedWebPage.reviews   

        prodData.reviews.push(countryData)
    })

    // this will be the resolved value of the promise that
    //   getProductDataFromManyDomains() returns
    return prodData;
}

// usage
getProductDataFromManyDomains(productNum).then(result => {
    console.log(result);
});

您也可以并行运行多个请求,而不是一次一个,但由于您最初试图让代码一次执行一个,因此我向您展示了如何做到这一点。

如果您想并行执行它们,您只需将 Promise 累积在一个数组中并使用 Promise.all() 知道它们何时全部完成,而您不会 await 请求。

这是并行运行请求的代码版本,使用 .map()Promise.all()

function getProductDataFromManyDomains(productNum) {
    let prodData = {
        reviews: []
    }

    const appCountries = [
        {countryCode: 'nl'}, 
        {countryCode: 'pl'},
        {countryCode: 'de'}
    ]

    return Promise.all(appCounteries.map(countryApp => {

        return scrapWebPage(countryApp, productNum).then(parsedWebPage => {
            let countryData = {}
            countryData.countryCode  = countryApp.countryCode
            countryData.ratingCount  = parsedWebPage.aggregateRating.ratingCount
            countryData.ratingValue  = parsedWebPage.aggregateRating.ratingValue
            countryData.reviews      = parsedWebPage.reviews 
            return countryData;         
        });
    })).then(results => {
        // put results into prodData and make that the resolved value
        prodData.reviews = results;
        return prodData;
    });
}

getProductDataFromManyDomains(productNum).then(result => {
    console.log(result);
});

【讨论】:

  • 从不同域提取数据的 HTTP 请求绝对应该异步执行。否则,同步执行会浪费大量时间。
  • @vitaly-t - OP 编写代码一次执行一个,所以看起来他们想这样做,这就是为什么我向他们展示如何这样做。如果他们愿意,我已经在我的答案中添加了更多信息,说明如何并行执行。
  • @vitaly-t - 我添加了一个并行运行请求的代码版本。
  • @jfriend00 哦!我不知道如果我只是让 func 异步它会自动返回承诺!这解决了我的问题 :) 关于同步拨打电话 - 是的,我知道这是个坏主意,但不想在我不舒服的东西上走得太远。我会做下一步:)
  • @stackustack - async/await 没有免费的午餐。它并没有真正“阻止”整个解释器的执行,它只是暂停一个函数,然后立即从该函数返回一个承诺。当该函数最终完成并返回时,该承诺将得到解决。所有async 函数都会自动返回一个promise。绝对值得充分理解这一点。因此,awaitasync 函数内工作以暂停执行,但不会在该函数之外暂停执行(外部世界会收到一个承诺,告诉它何时该函数实际完成)。
猜你喜欢
  • 2023-02-10
  • 2022-12-22
  • 2018-01-03
  • 1970-01-01
  • 1970-01-01
  • 2015-06-18
  • 2017-10-18
  • 2021-06-13
相关资源
最近更新 更多