Возврат из функции, которая использует несколько асинхронных вызовов - PullRequest
0 голосов
/ 18 мая 2018

Таким образом, основная идея заключалась в том, чтобы написать метод, который будет отбирать веб-страницу для получения данных 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 перед заполнением.Это возможно?Или какой тут хороший пример, чтобы иметь дело с такими вещами?

1 Ответ

0 голосов
/ 18 мая 2018

Используйте петлю for вместо .forEach().Цикл for будет приостановлен на время ожидания, цикл .forEach() не будет.Это происходит потому, что обратный вызов async, который вы передаете .forEach(), вернет обещание, но .forEach() не предназначен для того, чтобы что-то делать с этим обещанием, поэтому он не ожидает его разрешения, прежде чем продолжить цикл, а for цикл с использованием 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.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);
});
...