Angular 5 - array staje się pusty w innej funkcji

Odpowiedz Nowy wątek
2018-04-16 14:48
0

Witam.
Czy może mi ktoś w końcu wyjaśnić, jak idiocie, jak zmienne w Angular 5 (TypeScript) działają?
Mam obiekt typu Dokument:

export class Dokument {
    ID:number;
    KodNabywcy:string;
    KodOdbiorcy:string;
    KodPlatnika:string;
    NumerDokumentu:string;
    Numerator:number;
    TypDokumentu:number;
    DataWystawienia:string;
    DataOperacji:string;
    TerminPlatnosci:string;
    FormaPlatnosci:number;
    Bufor:number;
    LiczonyOd:number;
    VAT:number;
    Netto:number;
    Brutto:number;
    Waluta:string;
    Pozycje:any[];
}

Ten dokument posiada tablice Pozycje, które uzupełniam za pomocą okienka modal z wypełnioną tabelką towarami z użyciem datatables i jego multiselect.
Dodawanie wybranych pozycji:

  addNewPozycje() {
    var table = $('#towaryTable').DataTable();
    let rows = table.rows('.selected').data();
    let tempPozycje = [];
    for (let i = 0; i < rows.length; i++) {
      const element = rows[i];
      let kod = element[1];
      this.api.GetTowarDoDokumentu(kod, this.dokument.KodNabywcy).subscribe((result) => { // z api ściągam więcej info na temat tego towaru 
        tempPozycje.push(result[0]); // do tymczasowej tablicy dodaje sobie rekordy jakie przyjdą z API
      }, (error) => console.log(error), () => {
      });
    }
    this.dokument.Pozycje = tempPozycje;
    this.przeliczWartoscDokumentu(this.dokument.LiczonyOd); // tutaj liczę wartość dokumentu na podstawie podanych pozycji
    console.log(this.dokument.Pozycje); // konsola wyrzuca mi poprawną tablice elementów w tym miejscu
    $('#new-event-modal').modal('hide');
    table.rows().deselect();
  }

Funkcja liczenia wartości dokumentu, która Pozycje.length ma równe 0:

przeliczWartoscDokumentu(typ: number) {
    this.dokument.Brutto = 0;
    this.dokument.Netto = 0;
    let rabat = 0;
 
    console.log(this.dokument.Pozycje.length); // length = 0... Why?!
 
    for (let i = 0; i < this.dokument.Pozycje.length; i++) {
      const pozycja = this.dokument.Pozycje[i];
      if (typ == 1) {
        pozycja.Cena = this.obliczCeneNetto(pozycja.Cena, pozycja.Stawka);
      }
      else {
        pozycja.Cena = pozycja.Cena;
      }
      pozycja.Wartosc = this.round(pozycja.Ilosc * pozycja.Cena);
 
      if (pozycja.Stawka == 0) {
        pozycja.Netto = pozycja.Wartosc;
        pozycja.Brutto = pozycja.Wartosc;
      }
      else {
        if (typ == 2) {
          pozycja.Brutto = pozycja.Wartosc;
          pozycja.Netto = this.obliczCeneNetto(pozycja.Wartosc, pozycja.Stawka);
        }
        else if (typ == 1) {
          pozycja.Netto = pozycja.Wartosc;
          pozycja.Brutto = this.obliczCeneBrutto(pozycja.Wartosc, pozycja.Stawka);
        }
      }
      this.dokument.Brutto += pozycja.Brutto;
      this.dokument.Netto += pozycja.Netto;
      this.dokument.VAT = this.round(this.round(this.dokument.Brutto) - this.round(this.dokument.Netto));
    }
  }

Pozostało 580 znaków

2018-04-16 16:56

this.api.GetTowarDoDokumentu(...).subscribe rejestruje (kolejkuje) Promisea, który wykona się kiedyś w przyszłości (jest asynchroniczny).

Ty natomiast zakłądasz, że on wykona się od razu, i już kilka linijek niżej oczekujesz, że zmienna tempPozycje, wykorzystywana w tym Promiseie, będzie gotowa - rzecz jednak w tym, że część asynchroniczna kodu wykonuje się zawsze po części synchronicznej (poczytaj o event loop w JSie).

Przykład ilustrujący problem:

// Funkcja zwraca promise'a, który rozwiąże się (wykona) po 250 milisekundach, zwracając liczbę "128"
function buildMeSomePromise() {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve(128);
        }, 250);
    });
}
 
let number = 0;
 
// Tworzymy promise'a, kolejkując go do wykonania *kiedyś w przyszłości*
buildMeSomePromise().then((newNumber) => {
    number = newNumber;
});
 
// Nie czekamy na rezultat tamtego promise'a, tylko od razu lecimy dalej;
// Efekt: zmienna "number" wciąż trzyma starą wartość (zmieni ją dopiero za ok. 250 milisekund)
console.log(number);
 
// Po odczekaniu sekundy, zmienna będzie już miała nową wartość:
setTimeout(() => {
    console.log(number);
}, 1000);

W Twoim przypadku rozwiązaniem będzie podejście w stylu:

// Tablica "rowPromises" będzie zawierać listę wszystkich promise'ów utworzonych w wyniku przetwarzania wierszy:
let rowPromises = [];
 
// Utwórz promise'y:
// (można się także zabawić z Array.map! :-))
rows.forEach((row) => {
    let promise = this.api.GetTowarDoDokumentu(kod, this.dokument.KodNabywcy).subscribe(/* ... */) /* ... */;
 
    rowPromises.push(promise);
});
 
// Promise.all() tworzy nowy promise, czekający aż wszystkie podane promise'y w jego parametrze skończą swoje działanie
Promise.all(rowPromises).then(() => {
    /* reszta kodu tutaj */ 
});

Zauważ, że wykorzystanie asynchroniczności powoduje, że reszta kodu musi się do niego dostosować - nie możesz mieszać kodu asynchronicznego z synchronicznym. To znaczy możesz... ale będziesz przetwarzał stare dane, a nie nowe (tak jak u Ciebie) :-)

A z drugiej strony: nie możesz np. wykonywać zapytań HTTP synchronicznie, ponieważ to zablokowałoby przeglądarkę na czas ich wykonywania (strona zamroziłaby się na kilka sekund, co jest dosyć niepożądanym efektem ubocznym).


edytowany 8x, ostatnio: Patryk27, 2018-04-16 19:22
Ciężko tu mówić o race condition - to pojęcie nie ma tu raczej zastosowania, tu wszystko jest przewidywalne, tyle, że OP nie rozumie jak to działa (no chyba, że ktoś rzeczywiście zacznie "leczyć" asynchroniczność setTimeoutem :D ) - Maciej Cąderek 2018-04-16 19:04
Jako-tako race condition to nie jest (koniec końców mamy do czynienia z jednym wątkiem, nie wieloma) - lecz obserwowalny efekt jest podobny. - Patryk27 2018-04-16 19:06
Widziałem takie przykłady leczenia timeoutami - ayyy caramba :D - Patryk27 2018-04-16 19:07
No nie do końca, piszesz: oczekujesz, że zmienna tempPozycje, wykorzystywana w tym Promisie, będzie gotowa - jest to tzw. race condition (**ponieważ może być gotowa, ale nie musi** - zależy od tego, jak szybko wykona się zapytanie). - tu nie ma albo/albo - sytuacja jest jednoznaczna, wartość jest niedostępna, niezależnie od tego czy zapytanie trwało 0ms czy więcej - Maciej Cąderek 2018-04-16 19:14
Callback operacji asynchronicznej wykona się zawsze po kodzie synchronicznym. - Maciej Cąderek 2018-04-16 19:16
Ayyy caramba - no tak, masz całkowitą rację! Już poprawiam :-) - Patryk27 2018-04-16 19:20

Pozostało 580 znaków

2018-04-16 20:54
0

Spodziewałem się, że problem leży w asynchroniczności ale chyba nie po to to powstało żeby teraz wszystko wciskać w setTimeout(). Chce się dowiedzieć co robię źle skoro console.log() ma dane, a funkcja zliczająca wartość dokumentu nie. Nawet kolejność nie ma znaczenia.

    this.dokument.Pozycje = tempPozycje;
    this.przeliczWartoscDokumentu(this.dokument.LiczonyOd); // najpierw funkcja licząca
    console.log(this.dokument.Pozycje); // później logowanie do konsoli

czy

    this.dokument.Pozycje = tempPozycje;
    console.log(this.dokument.Pozycje); // najpierw logowanie do konsoli
    this.przeliczWartoscDokumentu(this.dokument.LiczonyOd); // później funkcja licząca

Konsola pokazuje wszystko poprawnie. Jeśli synchronicznie będzie mi to działać to wole, bo nie będę siedział całą szychtę i tracił czas na taką głupotę. Mogę prosić o przykład synchronicznego pobierania danych z API?

Pozostało 580 znaków

2018-04-16 20:56
0

Pokaż, co zwraca Ci konsola.

Jeśli synchronicznie będzie mi to działać to wole, bo nie będę siedział całą szychtę i tracił czas na taką głupotę

Dlaczego piszesz we frameworku opartym o asynchroniczność, bez uprzedniego zrozumienia na czym polega cały ten "bajer"? ;-)


edytowany 2x, ostatnio: Patryk27, 2018-04-16 20:56

Pozostało 580 znaków

2018-04-16 20:59
0

Rozumiem na czym polega bajera (ogólnikowo), nie rozumiem dlaczego większą "władzę" ma konsola i chapie wszystko, a moje niezbędne funkcjałki nie mają co liczyć ;-)

Pozostało 580 znaków

2018-04-16 21:01
0

Konsola nie ma większej władzy - nie masz tam przypadkiem jakiegoś gettera?
Rezultat pokazuje się od razu czy musisz coś kliknąć w konsoli, aby się dopiero załadował?


Pozostało 580 znaków

2018-04-16 21:09
0

Podczas dodawania pozycji do dokumentu liczę jego wartość i wtedy się to wszystko dzieje.
Szczegóły masz na priv, strona jest w wersji testowej, nieoficjalnej.

Pozostało 580 znaków

Odpowiedz
Liczba odpowiedzi na stronę

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

Robot: CCBot