• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

Javascript Pętla for i setTimeout

Object Storage Arubacloud
0 głosów
684 wizyt
pytanie zadane 18 sierpnia 2021 w JavaScript przez adek236 Nowicjusz (180 p.)
edycja 19 sierpnia 2021 przez adek236

Witam, uczę się JS od 3 tyg. To mój pierwszy post. Zacząłem sobie taki projekcik, gra karciana (typu MagicTheGathering) w przeglądarce. Teraz piszę ruchy komputera i utknąłem tzn nie wiem czego użyć (również ogólnie, żeby ruchy komputera odbywały się jedna po drugiej, tak, żeby było widać, że coś faktycznie robi, a nie wszystko dzieje się na raz), żeby to przeskoczyć. 

Problem polega na tym, że pętla robi kolejny obrót za czym setTimeout się skończy i usunie kartę. Co skutkuje tym, że poprzednia pętla znów działa na tej karcie (bo nie zdążyło jej usunąć), zamiast na następnej. (bez setTimeout działa dobrze, ale usuwa ją natychmiast i transform nie działa tzn nie zdąży zadziałać)

// e - to div z kartą
// cała funkcja również znajduje się w pętli
function pierwszyTargetKarta(jakiDmg) {
  const wystawioneKarty = wystKartyWalka.children;
  if (wystKartyWalka.children.length < 1) return false;
  
  Array.from(wystawioneKarty).forEach(e => {
    const hpTeraz = e.dataset.nowhp;
    
    if (jakiDmg >= hpTeraz && komp.juzZaatakowales === 0) {
      komp.juzZaatakowales = 1;
      init.iloscWystawionychKart--;
      e.style.transition = "transform 600ms";
      e.style.transform = "translate(0px, 300px)";
      setTimeout(() => {
        e.remove();
      }, 700);
    }
  }); 
}

  Próbowałem to zamknąć w funkcji asynchronicznej, ale właściwie robi to samo.

function pierwszyTargetKarta(jakiDmg) {
  const wystawioneKarty = wystKartyWalka.children;
  if (wystKartyWalka.children.length < 1) return false;
  
  Array.from(wystawioneKarty).forEach(e => {
    const hpTeraz = e.dataset.nowhp;
    
    (async () => {
      if (jakiDmg >= hpTeraz && komp.juzZaatakowales === 0) {
        komp.juzZaatakowales = 1;
        init.iloscWystawionychKart--;
        e.style.transition = "transform 600ms";
        e.style.transform = "translate(0px, 300px)";
        const usun = await usuwanieKarty(e);
      }
    })();
  }); 
}
function usuwanieKarty(e) {
  return new Promise(resolve => {
    setTimeout(() => {
      e.remove();
      console.log("usunalem")
      resolve('usunalem');
    }, 700);
  });
}

Może mnie ktoś naprowadzić czego użyć, ewentualnie jak to ugryźć. I co mogę używać do synchronizacji ruchów komputera (w sumie opóźnienia funkcji), czy async to dobry pomysł.

Mam nadzieje, że opisałem to w miarę zrozumiale.

Dzięki.

@edit 

Wdrążyłem w życie wasze propozycje i z async function sleep działa przy pierwszym okrążeniu, jak jest więcej elementów to po prostu znikają. 

Żeby to bardziej z wizualizować problem skróciłem do minimum kod i wrzuciłem na jsfiddle:

https://jsfiddle.net/pLyhdn8r/

 

komentarz 18 sierpnia 2021 przez ScriptyChris Mędrzec (190,190 p.)

// cała funkcja również znajduje się w pętli

W jakiej pętli wołana jest funkcja pierwszyTargetKarta?

komentarz 19 sierpnia 2021 przez adek236 Nowicjusz (180 p.)

@ScriptyChris, Wrzuciłem wyżej jak to mniej więcej wygląda.

2 odpowiedzi

0 głosów
odpowiedź 18 sierpnia 2021 przez ScriptyChris Mędrzec (190,190 p.)
wybrane 19 sierpnia 2021 przez adek236
 
Najlepsza

Z Twojego opisu nie do końca rozumiem w czym jest problem. Natomiast, jeśli chodzi o usunięcie elementu po zakończeniu tranzycji, to - zamiast ręcznie próbować synchronizować czas usunięcia elementu w setTimeout - bezpieczniej jest podpiąć się na elemencie na event transitionend i tam wykonać kod.

komentarz 19 sierpnia 2021 przez adek236 Nowicjusz (180 p.)
Przy użyciu transitionend, dalej for nie czeka, aż się to wykona. Ale bardzo przydatne, na pewno będę korzystał.
komentarz 19 sierpnia 2021 przez ScriptyChris Mędrzec (190,190 p.)
edycja 19 sierpnia 2021 przez ScriptyChris

Żeby pętla w funkcji zaatakuj czekała na rozwiązanie promisa, to powinieneś użyć await na funkcji pierwszyTargetKarta i ona powinna zwracać promisa. Bo nawet jeśli wewnątrz funkcji pierwszyTargetKarta tworzysz asynchroniczną IIFE, to ona jest odpalana w tle bez czekania.

Ja bym to przerobił w ten sposób:

function pierwszyTargetKarta() {
  console.log("pierwszy target")
  const obroncy = obrona.children;
  if (obrona.children.length < 1) return false;

  return Promise.all( // <- będzie czekać na zakończenie wszystkich promisów
    Array.from(obroncy).map(e => { // <- map pozwoli zwrócić tablicę promisów
      if (komp.ileAtaku >= komp.ileObrony && komp.juzZaatakowales === 0) {
        console.log("for obrona")
        komp.juzZaatakowales = 1;
     
        return new Promise((resolve) => { // <- zwracasz własnego promisa
          e.addEventListener('transitionend', resolve); // <- rozwiąż promisa po zakończeniu tranzycji

          e.style.transition = "transform 600ms";
          e.style.transform = "translate(0px, 50px)";
          e.remove();
        });
      }
    })
  )
}

I wtedy wywołanie: await pierwszyTargetKarta()


Dodatkowo: e.remove(); wrzuć do środka transitionend event listenera, przed wywołaniem resolve.

komentarz 19 sierpnia 2021 przez adek236 Nowicjusz (180 p.)

Działa, dzięki. To właściwie chyba mogę wszystkie ruchy kompa zrobić w ten sposób. W async funkcjach lepiej używać zwykłego for czy for of, bo forEach nie działa z await ? I czy mogę używać e.ontransitionend = () => {}, czy lepiej przyzwyczajać się do event listenera ?

komentarz 19 sierpnia 2021 przez ScriptyChris Mędrzec (190,190 p.)

async funkcjach lepiej używać zwykłego for czy for of, bo forEach nie działa z await ?

Zależy od przypadku użycia, ale w każdej z tych pętli możesz użyć słówka awaitfor..of ma nawet swój odpowiednik for..await..of. Metoda forEach działa z await, tylko że sama metoda zwraca undefined, więc - bez jakiegoś wrappera lub zmiany na np. map - nie dowiesz się na zewnątrz, gdy promisy tworzone w konkretnych callbackach zostały rozwiązane.

I czy mogę używać e.ontransitionend = () => {}, czy lepiej przyzwyczajać się do event listenera ?

Korzystanie z interfejsu event listenera oferuje więcej możliwości niż zwykły event handler:

  • wygodne podpinanie wielu listenerów do jednego zdarzenia
  • selektywne usuwanie listenerów
  • usunięcie wlelu listenerów na raz przy użyciu AbortSignal
  • dodatkowe opcje, jak automatyczne usuwanie listenera po pierwszym obsłużonym zdarzeniu czy wybranie fazy, w której listener będzie działać.
komentarz 19 sierpnia 2021 przez adek236 Nowicjusz (180 p.)
Ok, dzięki.
komentarz 19 sierpnia 2021 przez adek236 Nowicjusz (180 p.)

@ScriptyChris, Może jeszcze spytam, żeby nie robić nowego tematu. Czy da się jakoś uchronić przed zmianą danych po prostu w przeglądarce (klikając 'zbadaj' i zmieniając w elements), które ustawiam za pomocą dataset, czy po prostu trzeba inaczej budować kod? 

komentarz 19 sierpnia 2021 przez ScriptyChris Mędrzec (190,190 p.)

Nie da się ani ukryć kodu frontendowego ani uchronić go przed modyfikacją po stronie klienta. Nawet jeśli zablokujesz skróty klawiszowe odpowiedzialne za otwarcie devtoolsów przeglądarki, to i tak można tam wejść z poziomu menu samej przeglądarki, nie wspominając o możliwości auto-startu devtoolsów z poziomu odpowiedniej flagi w pliku uruchomieniowym, czy podobnej funkcjonalności w oprogramowaniu automatyzującym obsługę przeglądarki.

Możesz oczywiście kod minifikować i obfuskować, ale są narzędzia odwracające te procesy i, jeśli ktoś będzie chciał i potrafił, to odczyta kod frontendu. M.in. z tego powodu dobrze jest traktować kod wystawiany do klienta jako potencjalnie niebezpieczny dla aplikacji po stronie backendu - pamiętaj, żeby zawsze walidować to, co odbierasz w swoim API na backendzie. Dodatkowo, warto na froncie stosować mechanizmy ochronne typu Content Security Policy.

0 głosów
odpowiedź 18 sierpnia 2021 przez Wiciorny Ekspert (270,170 p.)

W zakresie tego co możesz użyć do synchronizacji ruchów komputera polecam tutaj poczytać 

https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events -> np z wykorzystaniem ruchów myszką, generalnie opóźnienie funkcji to faktycznie możnaby rozwiązać timeoutem,  jako callback 

Tylko ten set-timeout to musi się wykonać, a nie zwracać obietnice, np i oczekiwać rozwiązania

 await new Promise(resolve => setTimeout(resolve, 1000));

w ten sposób mógłbyś to rozwiązać, spróbować spowolnić pętle np. poprzez oczekiwanie możesz też w taki sposób 

https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Timeouts_and_intervals  poczytaj może interwałowo coś wykonywać, albo po prostu to wywołać. 

Odpal sobie ten schemat : 

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function demo() {
  console.log('Taking a break...');
  await sleep(2000);
  console.log('Two seconds later, showing sleep in a loop...');

  // Sleep in loop
  for (let i = 0; i < 5; i++) {
    if (i === 3)
      await sleep(2000);
    console.log(i);
  }
}

demo();

 

komentarz 19 sierpnia 2021 przez adek236 Nowicjusz (180 p.)

W pętli forEach wyskakiwał błąd:

await is only valid in async functions and the top level bodies of modules

Zmieniłem na zwykłą for i działa tylko na pierwszym okrążeniu, potem po prostu znikają. Wyżej wrzuciłem mniej więcej o co mi chodzi.

Podobne pytania

+1 głos
3 odpowiedzi 540 wizyt
pytanie zadane 26 kwietnia 2021 w JavaScript przez molik Użytkownik (950 p.)
+1 głos
3 odpowiedzi 1,106 wizyt
pytanie zadane 18 stycznia 2018 w JavaScript przez Artek Stary wyjadacz (11,800 p.)
+1 głos
2 odpowiedzi 889 wizyt
pytanie zadane 17 lutego 2017 w JavaScript przez lukaszlukasz Nowicjusz (170 p.)

92,575 zapytań

141,424 odpowiedzi

319,649 komentarzy

61,961 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj.

Akademia Sekuraka

Kolejna edycja największej imprezy hakerskiej w Polsce, czyli Mega Sekurak Hacking Party odbędzie się już 20 maja 2024r. Z tej okazji mamy dla Was kod: pasjamshp - jeżeli wpiszecie go w koszyku, to wówczas otrzymacie 40% zniżki na bilet w wersji standard!

Więcej informacji na temat imprezy znajdziecie tutaj. Dziękujemy ekipie Sekuraka za taką fajną zniżkę dla wszystkich Pasjonatów!

Akademia Sekuraka

Niedawno wystartował dodruk tej świetnej, rozchwytywanej książki (około 940 stron). Mamy dla Was kod: pasja (wpiszcie go w koszyku), dzięki któremu otrzymujemy 10% zniżki - dziękujemy zaprzyjaźnionej ekipie Sekuraka za taki bonus dla Pasjonatów! Książka to pierwszy tom z serii o ITsec, który łagodnie wprowadzi w świat bezpieczeństwa IT każdą osobę - warto, polecamy!

...