Asynchronicznosc w serii w Angularze

0

Witam!

Mam program, ktory poprzez formularz tworzy liste zakupow. Obecnie pisze wersje z wykorzystaniem asynchronicznych zadan HTTP. Problem wystepuje przy dodawaniu nowych elementow(jesli dodawanie elementu o numer wiekszego niz ostatni na liscie to dziala), a dokladniej przy dodawaniu elementu o id, ktory juz istnieje. Rozwiazuje to tak, ze dodaje nowy element do listy, tuz za koncem i przypisuje wartosc zakupu poprzedniego kolejnemu, w kolejnosci od ostatniego az do elementu o zadanym id. Nastepnie dla zadanego id wpisuje nowa wartosc.

Np. gdy:

  1. Jablko.
  2. Gruszka.
  3. Chleb.

i dodaje 1. Bulka to robie tak:
4. cos.

nastepnie 4. cos zamieniam na -> 4.Chleb.,3.Chleb -> 3.Gruszka, 2. Gruszka -> 2. Jablko i 1. Jablko -> 1. Bulka.

W kodzie rozwiazuje to tak, ze najpierw tworze wymagana ilosc obietnic w tablicy obietnic, a nastepnie przy uzyciu tych obietnic wykonuje jedna po drugiej i na koncu przypisuje nowa wartosc.

Spodziewalem sie, ze program wykona sie w serii jedna po drugiej tworzenie obietnic, nastepnie w serii jedna po drugiej wywolanie obietnic i na koncu po nich ustawienie dodawanej wartosci zakupu. Czesto tak jest, ze dziala dobrze, ale czasem kolejnosc jest zaburzona i wartosci przy id nie sa w kolejnosci w jakiej powinny byc(i pomagajac sobie wypisywaniem wartosci widze czasem, ze np. przypisanie wartosci o id 2 do id 3 nastapilo przed przypisaniem wartosci o id 3 do wartosci o id 4). Czy przy podanym kodzie jest to mozliwe? A jesli tak, to ktory fragment dziala zle?

Zamieszczam pogladowy kod:
wywolanie:

this.iterateAndCreatePromises(promises, to, k, i, id,tresc);  //wywolanie, promises tutaj jeszcze puste,to przechowuje oryginalne this, k poczatkowe to ostatni numer zakupu
                                                                                              //id to numer zakupu do dodania, a tresc to tresc zakupu do dodania

pierwsza funkcja:

iterateAndCreatePromises(promises: any,to: any,k: number,i: number,id:number,tresc: string) {

    promises.push(new Promise((resolve) => {    //utworzenie w tablicy obietnic kolejnych obietnic, jesli dobrze rozumiem przy tworzeniu tych obietnic nic sie nie wywoluje wewnatrz,
                                                                            //dopiero w drugiej funkcji

      to.model.ustawZakup(k, to.model.zwrocZakup(i).nazwa, k, to); //przesuwanie zakupow kolejno o jeden numer identyfikatora nizej

      console.log("Sprawdz! i k: " + k);

      console.log("Numer k przy iterowaniu obietnic: " + k);

      k--;

      resolve();

    }));

    if (k >= id) {

      this.iterateAndCreatePromises(promises, to, k, i, id,tresc);

    }
    else this.iterateAndDoPromises(promises, 0, to, id, tresc);

  }

druga funkcja:

iterateAndDoPromises(promises: any,p: number,to: any,id: number,tresc: string,) {

  promises[p].then(() => {          // when done

    console.log("Obietnica o numerze: " + p);

    if (promises[++p]) this.iterateAndDoPromises(promises, p,to,id,tresc); //wywolaj kolejna obietnice
    else to.model.ustawZakup(id, tresc, to);

  });
}

Bardzo prosze o pomoc w naprawieniu programu, aby dzialal poprawnie, o wskazanie bledow, bo sam juz nie wiem czemu dziala tak jak opisalem.

1

Obietnice mają to do siebie, że są asynchroniczne, a callback może mutować stan, do którego ma dostęp. W ogóle to co robisz wygląda nieciekawie. Po co Ci tu Promise?

0

@SkrzydlatyWąż: Szukalem sposobu jak wykonac asynchroniczne funkcje jedna zaraz po drugiej w serii i zwykla petla z funkcjami asynchronicznymi wykonywala sie nie po kolei(wykonywaly sie wszystkie funkcje naraz).
To rozwiazanie dziala troche lepiej, ale nie idealnie.
Ogolnie mam problem jak wykonac poprawnie serie asynchronicznych funkcji w odpowiedniej kolejnosci. Wlasnie dlatego tutaj napisalem. Wyglada, ze nie potrafie wykonac prawidlowo funkcji asynchronicznych w serii w odpowiedniej kolejnosci.
Nie za bardzo tez mi dzialalo polaczenie async z await - w sumie to nie umiem w ogole tych mechanizmow uzyc tak, zeby efekt byl odpowiedni.
Jak rozumiec, ze callback moze mutowac stan - nie ma pewnosci, ze callback wykona sie dopiero po wykonaniu poprzedniej funkcji?
Moglbys mi dac rady jak osiagnac zamierzony efekt? A moze jakis (pseudo)kod?

0

w JS istnieje coś takiego jak kolejka zdarzeń, więc kontynuacje funkcji asynchronicznych, timeouty, inteval itp. nie mogą wykonać się na raz (w uproszczeniu). Obietnic używa się głównie po to, aby uzyskać jakiś rezultat wychodzący jako parametr w then lub error w catch. Zacznij od tego, aby wyrzucić logikę z new Promise(resolve) =>... Niech tam nie będzie modyfikacji zmiennych spoza Promise. Do resolve powinieneś przekazać to, co ma potem wyjść w then, czyli np. wynik zapytania do API, bo nie masz pewności co do kolejności wywołań operacji wewnątrz konstruktorów Promise. Zrób jeden Promise, w którym symulujesz żądanie, w then umieść kontynuację z logiką. Dla wielu żądań możesz użyć Promise.all.

Wykonywanie asynchronicznych funkcji jedna po drugiej mija się z celem. :) Po to są asynchroniczne, żeby nie były sekwencyjne.

0

Bardzo dziekuje za odpowiedzi!
Uzywalem tez w jednej z wersji Promise.all i efekt nie byl taki, jakbym oczekiwal. Dzialalo to podobnie, a nawet gorzej niz zamieszczony wyzej kod. W konsoli, gdzie uruchamiam serwer (json-server) widze tez czas trwania odpowiednich putow(uaktualnien oczywiscie) i zauwazylem, ze jesli jakas kolejna funkcja wykonuje sie krocej niz poprzednia, to czasem nie jest zachowana kolejnosc, widze to po wypisywaniu sobie wartosci. Jednym slowem przy promise.all zadziala poprawnie then dopiero po wykonaniu wszystkich obietnic, ale juz obietnice wewnatrz promise.all nie zawsze wykonuja sie po kolei, panuje nazwalbym to troche chaos.
To z punktu widzenia asynchronicznosci nie da sie zrobic takiej funkcjonalnosci jak opisalem? Mozna zrobic tylko jedno wywolanie funkcji asynchronicznej naraz, nie mozna sekwencyjnie?

0

Twój kod w ogóle wygląda źle i nie widać sensu zastosowania Promise. To nie chaos, a kolejka zdarzeń, a ona nie da gwarancji sekwencyjności.
Tak jak pisałem, możesz dzięki Promise.all wywołać kilka obietnic asynchronicznie na raz i poczekać na listę wyników, ale nie oczekuj, że konstruktory Promise wykonają się po kolei. One służą do tego, aby zwracać dane przez resolve albo reject, a nie żeby tam umieszczać logikę.

Powinieneś koncentrować się na wyniku, który zwróci Promise (lub listę z Promise.all) poprzez then i obsłudze errora, a nie na tym, co zajdzie wewnątrz konstruktora.

0

@SkrzydlatyWąż: Nie upieram sie, ze kod jest swietny. Tego typu rozwiazanie mialo na celu wykorzystanie wlasciwosci then obietnic, aby zapewnic odpowiednia kolejnosc wykonania, ale widac nie do konca to sie sprawdzilo.
Ponawiam pytanie, czy wywolywanie obietnicy po obietnicy poprzez then, jak u mnie wyzej w kodzie, nie daje gwarancji sekwencyjnosci? Jak to dziala, czasem then wykonuje sie przed skonczeniem wykonania funkcji asynchronicznej wewnatrz promise?
Chyba zrezygnuje z tej funkcjonalnosci - mozliwosci dodawania zakupu z id juz istniejacym, czyli jak wyzej.
Ale jesli np. chcialbym usunac zakup z serwera i listy w modelu, a nie jest on ostatnim zakupem. Np. srodkowym. To jak zmienic wszystkie id powyzej na o jeden mniej, tez wydaje mi sie, ze sekwencyjnie trzeba. Czy w serwerze nie zmienic, a wyswietlac poprawnie, dziwne to?

0

Promise nie służy do zachowywania kolejności i uruchamianie then w pętli nie zadziała tak jak tego oczekujesz. Działa to tak jak w dokumentacji, którą Ci podlinkowałem tutaj. Funkcja w konstruktorze Promise (executor) odkładana jest do kolejki i wywoływana asynchronicznie, czyli nie masz kontroli nad tym, kiedy to nastąpi. Po jej wykonaniu do kolejki trafia wywołanie then i też nie wiesz, kiedy to się stanie. Dlatego w then umieszczasz callback, który oczekuje na **wynik ** i wykonuje akcję, kiedy **wynik ** jest już dostępny.

A tu bardziej obrazowe wytłumaczenie:

Edit: Jeśli chcesz mieć sekwencyjny kod, to po prostu usuń Promise tam gdzie go nie potrzeba.

0

@SkrzydlatyWąż: Dzieki za linki. Bede ogladal i analizowal.
Tez probowalem na poczatku bez promise i wiele funkcji asynchronicznych uruchamialem sekwencyjnie i bardzo slabo to dzialalo. Wtedy to juz w ogole ani razu nie bylo odpowiedniej kolejnosci. Bo to co jest wewnatrz konstruktora promise to funkcja asynchroniczna(ta glowna). Juz nie wiem jak to ugryzc.
Bo z tego co wiem, funkcja asynchroniczna wywoluje sie jakis czas, a w tym czasie juz wykonuja sie funkcje standardowe/"synchroniczne". I tutaj zawsze mam z tym problem, zeby wymusic wykonanie kolejnej funkcji dopiero gdy wykona sie poprzednia funkcja asynchroniczna. A dodatkowo np. while jakis warunek nie spelniony to czekaj, zawiesza cala aplikacje. Licze,ze widac, ze myslalem nad tym i, ze mozna mi pomoc.

0

Nadal nie rozumiem po co jest Promise w kodzie który wstawiłeś.
Zacznij od tego, żeby pokazać gdzie w Twoim kodzie zaczyna się pierwsze wywołanie funkcji które wymusza asynchroniczność, np. gdzie robisz zapytanie do API / json server.

0

@SkrzydlatyWąż: Promise byl po to, zeby zachowac kolejnosc przy wywolywaniu funkcji. Bo wywolujac w petli nie bylo kolejnosci. Niestety nie dziala to jak chcialem. Czyli wyglada, ze nie ma to sensu. Wywolanie funkcji asynchronicznej jest tu:

to.model.ustawZakup(k, to.model.zwrocZakup(i).nazwa, k, to);   //"to" przechowuje oryginalne this, k poczatkowe 
                                                                                      //to ostatni numer zakupu,
                                                                                      //i to numer zakupu do dodania, a drugi parametr 
                                                                                      //to tresc zakupu do dodania

w ktorym jest:

this.zrodloDanych.uaktualnijZakup(zakup).subscribe( ...

a funkcja uaktualnijZakup wyglada tak:

uaktualnijZakup(zakup: Zakup): Observable<Zakup> {

   return this.http.put(`${this.url}/${zakup.id}`, zakup,options)
     .map(odpowiedz => odpowiedz.json());

 }
0

Nie o to chodzi, żeby blokować funkcje asynchronicze pętlami czy innymi pomysłami, ale o to właśnie, żeby nie blokowały przepływu. Dlatego należy dostarczyć kod, który będzie odpowiednią kontynuacją, a wstawia się go w then lub po await, w async function.
Od tego jest subscribe z RxJS, żeby przekazać tam funkcje, które wykonają się, kiedy zakończy się żądanie do API. Najprościej byłoby zwrócić observabla i uruchomić subscribe w komponencie Angulara, robiąc coś takiego:

onJakisButtonClick() {

  const zakup = ... //zbuduj zakup 
  this.zrodloDanych.uaktualnijZakup(zakup).subscribe(
     (odpowiedz) => { this.mojaListaZakupow = this.mojaListaZakupow.map(z => z.id === odpowiedz.id ? odpowiedz : z) }, // przykładowo, wykonaj odpowiednie przekształcenie na liście
     (error) => ...
}
0

@SkrzydlatyWąż: Ja wlasnie wywoluje funkcje this.iterateAndCreatePromises(promises, to, k, i, id,tresc); wewnatrz subscribe, a dokladniej juz w wykonaniu procedury obsługi powiadomienia o zakończeniu wykonywania z subscribe. Ale moze powinienem nie iterowac tego tak jak to robie, czyli kolejne wywolania put(w uproszczeniu) wewnatrz wykonania procedury obsługi powiadomienia o zakończeniu wykonywania w tym samym miejscu(czyli dobrze dziala zawsze tylko utworzenie nowego ostatniego elementu cos, a reszta jest nieoczywista), ale wywolywac analogicznie tylko dla kolejnych wykonan procedury obsługi powiadomienia o zakończeniu wykonywania z subscribe od uaktualnijZakup.Chyba tak sprobuje. Oby to bylo to.

0

@SkrzydlatyWąż: Bardzo dziekuje Tobie @SkrzydlatyWąż za pomoc. Jutro albo pojutrze(bo jutro swieto) bede dalej testowal, ale wyglada, ze chyba dziala!! Wstepnie nie widze bledow. Tyle roznych wywolan funkcji bylo, asynchronicznosc itp., troche sie dzialo. Wszystko wyprobowalem, naprawde oprocz tego, na co Ty mnie naprowadziles.
Otoz okazalo sie, ze jesli korzystam z wykonania procedury obsługi powiadomienia o zakonczeniu wykonywania z subscribe to jest pewnosc, ze asynchroniczna funkcja sie skonczyla. Wywolujac przy pomocy ifow(bo trzeba tez dac warunek stopu) kolejna funkcje asynchroniczna po skonczeniu poprzedniej, mozna w praktyce zarzadzac seria wywolan asynchronicznych. Przynajmniej tak to u mnie dziala.

0

Ogólnie zastanów się, czy to co robisz, czyli PUTy i granie w ping ponga z API, ma sens. Jeśli dodajesz nowy obiekt, to serwer powinien Ci zwrócić obiekt z wygenerowanym id. Aktualizujesz potem obiekt według tego id, podmieniasz na liście na widoku i tyle. Jeśli potrzebujesz liczby porządkowej od 1 do n to generuj ją jako indeks na liście + 1 i wyświetl, a nie mieszaj z identyfikatorem obiektu.

0

@SkrzydlatyWąż: Pomogles mi, widze, ze wiesz co robisz, dlatego przemysle to. Pozdrawiam! Milego wieczoru!

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