Łańcuch .then po fetchu

0

Cześć, jak mogę osiągnąć następujący efekt ? Chce aby dopóki moja pętla nie skończy obliczeń skrypt nie przechodził do kolejnego .then. Oto mój kod:

            .then((data) => {
                let incomesSum = 0
                new Promise((resolve, reject) => {
                    this.setState({loadingInformation: 'Calculating companies incomes, please wait ...'}, () => {
                        data.incomes.forEach((income, i) => {
                            incomesSum += Number(income.value)

                            // Resolve promise when all calculations are done
                            if(i === data.incomes.length - 1) {
                                resolve(Math.round(incomesSum))
                            }
                        })
                    })
                })
            })

            // I want this to start when my promise is resolved (loop ended calculations)
            .then((incomesSum) => {
2

Brakuje Ci return przed new Promise i

if(i === data.incomes.length - 1) {
    resolve(Math.round(incomesSum))
}

chyba można resolve zrobić po forEach?

0

Na boga wywal te theny. Jaki jest sens używania promisa na cos co nawet nie dzieje się asynchronicznie (jest zwykłym mapem)? Wszystkie theny polecam zastąpic async/await, żeby mieć ladny kod, w którym samemu sie nie pobugisz.

0

Tak, właśnie uczę sie async await, bo chce pisać czysty i elegancki kod

3

jak masz then i chcesz zrobić łańcuch, to musisz zwrócić albo wartość albo promisa, czyli np.

Promise.resolve(10) // opakowuje wartość 10 w obiekt promise, który się rozwiązuje do wartości 10
   .then(value => value + 9)  // dostajesz 10 i zwracasz 10 + 9 
   .then(value => Promise.resolve(value + 23)) // dostajesz 19 i zwracasz promise, który rozwiąże się do 19 + 23
  .then(value => console.log(value)); // dostajesz 42 

czyli musisz zwrócić albo wprost wartość, albo wartość opakowaną w obiekt promise (tutaj dla przykładu użyłem Promise.resolve, ale przecież każdy Promise to po prostu taka opakowana przyszła wartość, a then odpakowuje te wartości). I kolejne then się wywoła dopiero wtedy, kiedy dany promise się rozwiąże. Jak nic nie zwracasz, to kolejny then dostanie undefined.

A zobacz, że ty tutaj nic nie zwracasz:

       new Promise((resolve, reject) => { // jak to ma działać, skoro ten new Promise nie jest nigdzie "łapany"...?

czyli prędzej powino być return new Promise(...............

Tyle, że ten twój kod i tak nie ma wiele sensu:

                        if(i === data.incomes.length - 1) {
                                resolve(Math.round(incomesSum))
                            }

To wygląda jak syndrom zmęczenia przy pisaniu kodu, gdzie człowiek jest tak zmęczony po iluś godzinach klepania, że robi dziwne rzeczy (ew. syndrom niezrozumienia, jak działa forEach).

Bo przecież wystarczy przenieść ten kod poza forEach i nie będzie potrzebny żaden if.

     data.incomes.forEach((income, i) => {
        incomesSum += Number(income.value)
     });
    resolve(Math.round(incomesSum)); 

chociaż nie rozumiem całego kontekstu. To jakaś apka reactowa, że setState? I czemu czekasz na callback? Przecież i tak masz już wcześniej te dane. Poza tym po co ustawiać "loadingInformation" w stanie do czegoś, co się wywoła ułamek sekundy?

No i zamiast forEach, można napisać prościej te zliczanie sumy używając reduce. Więc moim zdaniem ta cała funkcja, w której jest teraz new Promise, forEach i this.setState, tak powinna raczej wyglądać:

.then((data) => {
     return Math.round(data.incomes.reduce((acc, income) => acc + income.value, 0));
})

Co do this.setState, to jeśli potrzeba ustawić stan po odebraniu/przemieleniu danych, to można to zrobić będzie w kolejnym then;

.then((data) => {
     return Math.round(data.incomes.reduce((acc, income) => acc + income.value, 0));
}
.then(incomeSum => {
    this.setState({incomeSum: incomeSum}); // albo { incomeSum } używając destructuringu
})

No i incomeSum jakoś brzmi bardziej po ludzku niż incomesSum, które brzmi dość dziwnie. Chociaż też nie wiem. Może totalIncome byłoby lepsze?

0

@LukeJL: Fakt, dzisiaj już mi się ciężko myśli, ale dzięki wielki za konkretne wytłumaczenie!. Prawda jest taka, że się trochę pogubiłem w swoim kodzie i zacząłem grzebać tam gdzie nie było problemu. Zrobiłem refaktor tej funkcji, aby cała funkcja działała asynchronicznie wystarczyło zrobił asynchronicznego fetcha. Loading information musi być bo całość trwa z 20 sekund, a komponent jest loaderem. Zrezygnowałem jednak z thenów i napisałem takie coś . Uważasz, że takie rozwiązanie jest dość eleganckie ?

    getAndCalculateCompaniesIncomes = async() => {
        for (const [i, company] of this.state.fetchedCompanies.entries()) {
            const url = 'secret' + company.id
            const data = await (await fetch(url).catch(this.handleError)).json()
            this.setState({loadingInformation: 'Calculating companies incomes, please wait...'}, () => {
                const incomesSum = data.incomes.reduce((acc, income) =>  acc + Number(income.value), 0 )
                this.setState(() => { company.totalIncomes = Math.round(incomesSum) }, () => {
                    const percentageLoadingProgress = (((i+1)/this.state.fetchedCompanies.length) * 100).toFixed(2)
                    this.setState({ percentageLoadingProgress }, () => {
                        if(this.state.percentageLoadingProgress == 100) {
                            this.sortCompaniesByTotalIncome()
                        }
                    })
                })
            })
        }
    }
4

Zrobiłeś coś niesamowitego. Użyłeś async / await do tego, żeby zrobić callback hell xD A to właśnie ten problem (nieczytelnego zagnieżdżania callbacków) miały rozwiązywać najpierw promisy, a potem async/await. To trochę jak zakładać maseczkę na brodę ;)

ogólnie w tym przypadku te callbacki do this.setState są niepotrzebne, bo na co czekasz w tym momencie? Powinieneś ustawić stan i już. Callback w this.setState jest opcjonalny, a nie obowiązkowy.

Loading information musi być bo całość trwa z 20 sekund, a komponent jest loaderem.

Co trwa 20 sekund? Bo na pewno nie ten kawałek:

 const incomesSum = data.incomes.reduce((acc, income) =>  acc + Number(income.value), 0 )

^ To się powinno uruchomić w niezauważalny dla człowieka ułamek sekundy. A właśnie przed tym kawałkiem najpierw ustawiasz setState, a potem jeszcze raz kolejny.

To za to może trwać już na tyle długo, że może być potrzebne zasygnalizowanie tego użytkownikowi:

 const data = await (await fetch(url).catch(this.handleError)).json()

ale nie robisz tego w żaden sposób.

A mógłbyś zrobić np. tak:

 this.setState({loadingInformation: 'Fetching data, please wait...'});
 const data = await (await fetch(url).catch(this.handleError)).json()
 this.setState({loadingInformation: ''});

z drugiej strony czemu aż 20 sekund u ciebie to trwa? Tu jest bolączka, bo 20 sekund to dość długo. A może np. sam sobie generujesz te 20 sekund? (bo jeśli dajesz do każdego this.setState callback, to sam opóźniasz apkę, bo musi czekać na zmianę stanu. Co prawda wątpię, żeby to trwało aż 20 sekund, prędzej obstawiam, że mniej niż sekundę, ale kto wie. W sumie można by sprawdzić, co zajmuje ci tyle czasu. Chociaż... dużo pobieranych danych i wolny serwer... Ale z drugiej strony, jeśli masz faktycznie dużo (setki tysięcy?) danych, to i samo reduce może faktycznie wolno trwać. Ale to trzeba sprawdzić - masz dev toolsy, mógłbyś sprawdzić, co ci tyle zajmuje).

0

Dobra mistrzu to tak.20 sekund trwa ta cała pętla, bo jest tam 300 api call i z każdej odpowiedzi jest 100 obliczeń i nie da się inaczej tego zrobić, takie api dlatego napisałem loader mój błąd że setState z loading information jest wewnątrz pętli co może mylić, powinno być to przed całą pętlą. To jak mogę zrobić setStaty w których chcę mieć pewność, że jeden wywoła się po drugim jak nie w callbacku ? W sumie jak wywale ten pierwszy setState przed pętlę z loadingInformation to już będzie jeden zagnieżdżony setState, więc chyba nie jest to callback hell ? ;)

2

Dobra mistrzu to tak.20 sekund trwa ta cała pętla, bo jest tam 300 api call i z każdej odpowiedzi jest 100 obliczeń i nie da się inaczej tego zrobić, takie api dlatego napisałem loader mój błąd że setState z

Dlaczego jest 300 wywołań do API? Nie możesz zrobić z tego jednego, góra kilka wywołań? Kto projektuje to API? Bo już tutaj widzę problem. Poza frontendem, to taką ilością wywołań łatwo można serwer zarżnąć (albo wbić się w jakieś limity użytkowania, jeśli to zewnętrzna usługa).

1 użytkowników online, w tym zalogowanych: 0, gości: 1