Moduł9 - Zajęcia 17 - Minutniki i czas

Minutniki i czas

Cały kod JavaScript jest wykonywany przez procesor komputera. Kod, który do tej pory napisaliśmy, był synchroniczny, czyli zajmował procesor przez cały czas jego wykonywania. Przez to na przykład szybkość pętli do iteracji po tablicy zależy od szybkości procesora.

Istnieją operacje, które wchodzą w interakcję ze światem zewnętrznym. Przykładem będzie wymiana danych z serwerem przez sieć, co jest znacznie wolniejsze niż pobieranie ich z pamięci. Jeżeli takie operacje są przetwarzane synchronicznie, procesor musiałby czekać bezczynnie, podczas gdy żądanie sieciowe do serwera jest w toku, zamiast wykonywać inne instrukcje.

Kod synchroniczny jest wykonywany sekwencyjnie, każda instrukcja czeka na wykonanie poprzedniej. Gdy wywołujesz funkcję, która wykonuje długotrwałą akcję, zatrzymujesz program na cały czas jego wykonywania. Oznacza to, że w modelu programowania synchronicznego wszystko dzieje się po kolei.

Wyobraź sobie kolejkę za biletami na dworcu. Nie możesz zacząć kupować biletu, dopóki nie kupi go osoba przed tobą. Również osoby stojące za tobą nie mogą zacząć kupować biletów, dopóki Ty nie kupisz swojego.

W kodzie asynchronicznym wiele operacji może być wykonywanych jednocześnie. W tym modelu, żądanie sieciowe do serwera nie zatrzyma programu, będzie on kontynuował wykonywanie innych operacji. Po zakończeniu działania żądania program jest o tym informowany i uzyskuje dostęp do wyniku (np. danych z serwera).

Wyobraź sobie obiad w restauracji. Ty i inni goście zamawiacie jedzenie. Nie musisz czekać, aż im przyniosą jedzenie przed złożeniem swojego zamówienia. Podobnie inni odwiedzający nie muszą czekać, aż otrzymasz danie i zjesz, zanim będą mogli zamówić. Każdy otrzyma swoje danie, gdy tylko zostanie przygotowane.

Rozważmy różnicę w przykładzie, w którym program wysyła do serwera dwa żądania sieciowe, a następnie przetwarza ich wynik. Operacje 1 i 2 to funkcje, które wysyłają żądania do serwera, a 3,4 i 5 to dowolny inny synchroniczny kod, który znasz.

W modelu synchronicznym wszystko jest jasne i efekt jest raczej smutny - wszystkie operacje blokują wykonanie kolejnych aż do ich zakończenia. Jeśli operacje 3-5 przetwarzają kliknięcia użytkownika, interfejs po prostu zawiesi się, dopóki wyniki żądań 1-2 nie zostaną zakończone i przetworzone

Załóżmy, że użytkownik opublikował komentarz (co realizuje się przez żądanie sieciowe) i jednocześnie chciał otworzyć pasek boczny z najnowszymi wiadomościami. Po kliknięciu wyślij komentarz, interfejs zawiesi się i nie będzie reagował na jego działania, dopóki wynik wysłania komentarza nie nadejdzie z serwera. Oczywiście jest to niedopuszczalne.

W modelu asynchronicznym rozpoczęcie żądania sieciowego powoduje rozwidlenie, czyli uruchomienie żądania i wynikiem jego przetworzenia są różne akcje. Podczas wykonywania żądania program kontynuuje działanie i wykonuje inny kod. Gdy żądanie sieciowe zostanie zakończone, program może rozpocząć przetwarzanie jego wyniku, gdy tylko będzie wolny. Oznacza to, że użytkownik opublikował komentarz i mógł od razu otworzyć pasek boczny z najnowszymi wiadomościami, nie czekając na odpowiedź z serwera.

W jednostce czasu można wykonać tylko jedną operację, ponieważ JavaScript jest jednowątkowy. Programowanie asynchroniczne jest osiągane przez odroczone wywołania funkcji, w których inicjowanie operacji asynchronicznej i przetwarzanie jej wyniku to różne akcje.

Kod asynchroniczny

W kodzie synchronicznym kolejna instrukcja nie może rozpocząć wykonywania, dopóki nie zostanie wykonana poprzednia. Oznacza to, że instrukcje są przetwarzane sekwencyjnie.

            console.log("First log");
            console.log("Second log");
            console.log("Third log");
          

Poniższy kod jest asynchroniczny. Z funkcją setTimeout() zapoznamy się później. Teraz musimy wiedzieć tylko to, że przyjmuje dwa parametry: funkcję wywołania zwrotnego, która zostanie wywołana po N milisekund co przekażemy jako drugi argument.

            // Will run first
            console.log("First log");

            setTimeout(() => {
                // Will run last, after 2000 milliseconds
                console.log("Second log");
            }, 2000);

            // Will run second
            console.log("Third log");
          

Funkcja setTimeout() wykonuje się synchronicznie i rejestruje odroczone wywołanie przekazanej funkcji wywołania zwrotnego, która zostanie wywołana asynchronicznie po określonym czasie.

Wielowątkowość

Nie myl asynchroniczności i wielowątkowości (współbieżności) - to różne modele programowania. Podamy prostą analogię, która może rozjaśnić różnicę. Wyobraź sobie, że jesteś szefem kuchni w restauracji i masz zamówienie na kawę i tosty.

  • Podejście synchroniczne jednostrumieniowe - najpierw przygotowujesz kawę, następnie tosty i podajesz, a potem sprzątasz w kuchni.
  • Asynchroniczne podejście jednowątkowe - zaczynasz robić kawę i ustawiasz minutnik, następnie zaczynasz robić tosty i ustawiasz minutnik w ten sam sposób. Podczas przygotowywania kawy i tostów sprzątasz kuchnię. Kiedy dzwoni minutnik, zdejmujesz kawę z ognia, wyjmujesz tosty i podajesz.
  • Podejście wielowątkowe (współbieżność) - zatrudniasz dwóch asystentów, jednego do parzenia kawy i drugiego do robienia tostów. Teraz masz problem z zarządzaniem asystentami (wątkami), aby nie kolidowali ze sobą w kuchni podczas dzielenia się zasobami.

W asynchronicznych procesach jednowątkowych istnieje harmonogram zadań, w którym niektóre zadania zależą od wyników innych. Po zakończeniu każdego zadania wywoływany jest kod w celu przetworzenia jego wyniku. Potrzebujesz jednak tylko jednego pracownika do wykonania wszystkich zadań, a nie jednego pracownika na jedno zadanie.

Liczniki czasu

Wewnętrzny harmonogram czasowy przeglądarki umożliwia odroczenie wywołania funkcji na określony czas. Istnieją metody które pozwalają kontrolować, kiedy i jak często funkcja jest wywoływana. Timery są zaimplementowane w przeglądarce, a nie wbudowane w język i są dostępne w globalnym obiekcie window.

Limit czasu

Metoda setTimeout() umożliwia zaplanowanie uruchomienia funkcji po określonym czasie (N milisekund).

            const timerId = setTimeout(callback, delay, arg1, arg2, ...);
          
  • callback - funkcja, której wykonanie należy zaplanować.
  • delay - czas w milisekundach, po którym funkcja callback zostanie jednorazowo wywołana.

Dodatkowe argumenty (arg1, arg2 itd.) zostaną przekazane do funkcji wywołania zwrotnego w czasie wywołania. Zwraca numeryczny identyfikator utworzonego timera, który służy do jego usunięcia.

Przykład -------------------------------

const exampleResults = document.querySelector('div.example-results-s2a2');
const button = document.querySelector('button.btn-s2a2');
let phrase;
const onClick = () => {
  if (!exampleResults.querySelector('pre')) {
    phrase = document.createElement('pre');
  }

  const timerId = setTimeout(() => {
    phrase.textContent += `I love async JS!\n`;
    exampleResults.prepend(phrase);
  }, 2000);

  phrase.textContent += `timerId: ${timerId}\t`;
  exampleResults.prepend(phrase);
};

button.addEventListener('click', onClick);
            

PrzykładEND -------------------------------

Jeśli z jakiegoś powodu musimy anulować wywołanie funkcji zanim upłynie zdefiniowany czas, możemy użyć metody clearTimeout(id), która przyjmuje identyfikator timera i czyści go (usuwa).

            const greet = () => {
                console.log("Hello!");
            };
            const timerId = setTimeout(greet, 3000);
            clearTimeout(timerId);
          

Ponieważ wywołaliśmy clearTimeout(), co zostanie wykonane przed wywołaniem funkcji greet(), timer z timerId zostanie usunięty, a oczekujące wywołanie greet() zostanie wyrejestrowane. Z tego powodu nic nie zostanie wyprowadzone na konsolę.

Interwał

Metoda setInterval() to prosty sposób na powtarzanie tego samego kodu w kółko co określoną ilość czasu. Składnia i parametry są takie same jak setTimeout(). W przeciwieństwie do setTimeout(), interwał wykona funkcję nie raz, ale regularnie powtarza ją po określonym przedziale czasu. Możesz zatrzymać wykonywanie, wywołując metodę clearInterval(id).

            const timerId = setInterval(callback, delay, arg1, arg2, ...);
          

Przykład -------------------------------

Po kliknięciu przycisku „Start" rozpoczniemy interwał i co sekundę będziemy wyprowadzać string do konsoli. Używamy Math.random(), aby ciągi były różne. Klikając przycisk „Stop", wywołamy clearInterval() i przekażemy identyfikator interwału, który ma zostać zatrzymany.

const exampleResults_s2a3 = document.querySelector('div.example-results-s2a3');
const startBtn_s2a3 = document.querySelector('button.js-start-s2a3');
const stopBtn_s2a3 = document.querySelector('button.js-stop-s2a3');
let timerId_s2a3 = null;
let phrase_s2a3;
if (!exampleResults_s2a3.querySelector('pre')) {
  phrase_s2a3 = document.createElement('pre');
}
startBtn_s2a3.addEventListener('click', () => {
  timerId_s2a3 = setInterval(() => {
    phrase_s2a3.textContent += `I love async JS!  ${Math.random()}\n`;
    exampleResults_s2a3.prepend(phrase_s2a3);
  }, 1000);
});

stopBtn_s2a3.addEventListener('click', () => {
  clearInterval(timerId_s2a3);
  phrase_s2a3.textContent += `Interval with id ${timerId_s2a3} has stopped!\n`;
  exampleResults_s2a3.prepend(phrase_s2a3);
});
            

PrzykładEND -------------------------------

Częstotliwość uruchomienia licznika

Licznik przeglądarki ma najmniejsze możliwe opóźnienie. W nowoczesnych przeglądarkach waha się od około 0 do 4 milisekund. W starszych przeglądarkach opóźnienie może być dłuższe, do 15 milisekund. Zgodnie ze standardem minimalne opóźnienie wynosi 4 milisekundy, więc nie ma różnicy między setTimeout(callback, 1) i setTimeout(callback, 4).

Timer może uruchamiać się rzadziej niż określono w parametrze delay, ponieważ jeśli obciążenie procesora jest zbyt duże, niektóre uruchomienia funkcji interwałowych zostaną pominięte. Przeglądarki nadal wykonują timeouts i intervals, nawet gdy karta przeglądarki jest nieaktywna, ale zmniejszają częstotliwość uruchomienia licznika

Section3 Article1: Data i czas

Klasa Date abstrahuje większość pracy bezpośrednio z datami. Pozwala nam to reprezentować momenty w czasie jako obiekty i manipulować nimi za pomocą predefiniowanych metod. Korzystając z możliwości klasy Date można tworzyć zegary, liczniki, kalendarze i inne interaktywne elementy interfejsu związane z datami i godzinami.

Tworzenie daty

Instancja obiektu Date to obiekt reprezentujący określony punkt w czasie. Utworzenie daty bez argumentów zwraca obiekt przechowujący datę i dokładny czas w momencie jej inicjalizacji, czyli wtedy kiedy wyrażenie zostanie zinterpretowane. W przeglądarce podczas konwersji do string obiekt zwraca wynik wywołania metody toString(), więc w pierwszym logu otrzymujemy ciąg, a nie obiekt.

            const date = new Date();

            console.log(date);
            // "Fri Jun 18 2021 15:01:35 GMT+0300 (Eastern European Summer Time)"

            console.log(date.toString());
            // "Fri Jun 18 2021 15:01:35 GMT+0300 (Eastern European Summer Time)"
          

Ustawienie daty

Podczas tworzenia instancji klasy Date możesz ustawić datę z ciągu lub liczby. Ciąg może opisywać samą datę lub datę, godzinę minutę itd.

            const teamMeetingDate = new Date("March 16, 2030");
            console.log(teamMeetingDate);
            // "Mon Mar 16 2030 00:00:00 GMT+0200 (Eastern European Standard Time)"

            const preciseTeamMeetingDate = new Date("March 16, 2030 14:25:00");
            console.log(preciseTeamMeetingDate);
            // "Mon Mar 16 2030 14:25:00 GMT+0200 (Eastern European Standard Time)"
          

Ustawienie czasu poprzez podaniestring wewnętrznie wywołuje metodę Date.parse(), która konwertuje ciąg na liczbę - liczbę milisekund. Format przesyłanego ciągu jest bardzo elastyczny. Na przykład możesz pominąć zero dla dni i miesięcy. Spójrzmy na kilka przykładów, które zostaną poprawnie zinterpretowane..

            new Date("2030-03-16");
            new Date("2030-03");
            new Date("2018");
            new Date("03/16/2030");
            new Date("2030/03/16");
            new Date("2030/3/16");
            new Date("March 16, 2030");
            new Date("March 16, 2030 14:25:00");
            new Date("2030-03-16 14:25:00");
            new Date("2030-03-16T14:25:00");
            new Date("16 March 2030");
          

Innym sposobem tworzenia nowych obiektów jest przekazanie siedmiu argumentów o typie number opisujących po kolei:

  • rok
  • miesiąc (zaczynając od 0)
  • dzień
  • godzinę
  • minutę,
  • sekundę
  • milisekundę

Wymagane są tylko pierwsze trzy.

            const date = new Date(2030, 2, 16, 14, 25, 0, 0);
            console.log(date);
            // Sat Mar 16 2030 14:25:00 GMT+0200 (Eastern European Standard Time)
          

Metody

Instancja klasy Date ma wiele metod do odczytywania i zapisywania wartości daty i czasu. Metody zwracają lub przypisują rok, miesiąc, dzień miesiąca lub tygodnia, godzinę, minutę, sekundę i milisekundę dla każdej instancji. Dane te mogą być prezentowane w postaci string na podstawie lokalnego kalendarza lub języka.

Gettery

Gettery służą do odczytywania całej daty lub pojedynczej części. Przy użyciu w przeglądarce zwracana wartość zależy od aktualnej strefy czasowej ustawionej na komputerze użytkownika.

              const date = new Date();
              date  Wyświetla:  
              
              // Zwraca dzień miesiąca od 1 do 31
              date.getDate();  Wyświetla:  
              
              // Zwraca dzień tygodnia od 0 do 6
              date.getDay();  Wyświetla:  
              
              // Zwraca miesiąc od 0 do 11
              date.getMonth();  Wyświetla:  
              
              // Zwraca rok z 4 cyfr
              date.getFullYear();  Wyświetla:  
              
              // Zwraca godzinę
              date.getHours();  Wyświetla:  
              
              // Zwraca minuty
              date.getMinutes();  Wyświetla:  
              
              // Zwraca sekundy
              date.getSeconds();  Wyświetla:  
              
              // Zwraca milisekundy
              date.getMilliseconds();  Wyświetla:  

Istnieją równoważne wersje tych metod, które zwracają wartości w formacie UTC (Coordinated Universal Time), a nie dostosowane do bieżącej strefy czasowej użytkownika.

              const date = new Date();
              date  Wyświetla:  
              
              // Zwraca dzień miesiąca od 1 do 31
              date.getUTCDate();  Wyświetla:  
              
              // Zwraca dzień tygodnia od 0 do 6
              date.getUTCDay();  Wyświetla:  
              
              // Zwraca miesiąc od 0 do 11
              date.getUTCMonth();  Wyświetla:  
              
              // Zwraca rok z 4 cyfr
              date.getUTCFullYear();  Wyświetla:  
              
              // Zwraca godzinę
              date.getUTCHours();  Wyświetla:  
              
              // Zwraca minuty
              date.getUTCMinutes();  Wyświetla:  
              
              // Zwraca sekundy
              date.getUTCSeconds();  Wyświetla:  
              
              // Zwraca milisekundy
              date.getUTCMilliseconds();  Wyświetla:  

Settery

Wszystko, co można odczytać, można również nadpisać, metody zapisu są również nazywane tak jak gettery, ale zaczynają się od przedrostka set. Ponadto wszystkie metody mają swój odpowiednik w UTC.

              const date = new Date("March 16, 2030 14:25:00");
              date  Wyświetla:  
              
              date.setMinutes(50);
              date  Wyświetla:  
              
              date.setFullYear(2040, 4, 8);
              date  Wyświetla:  

Formatowanie daty

Obiekt daty może być reprezentowany w różnych formatach ciągów i liczb. Jest na to kilka metod. Na przykład toString(), toDateString() i toTimeString() zwracają standardową reprezentację ciągu, nie zakodowaną na stałe w standardzie, ale zależną od przeglądarki. Jedynym wymaganiem jest czytelność dla człowieka. Metoda toString() zwraca całą datę, a toDateString() i toTimeString() - odpowiednio tylko datę i godzinę.

              const date = new Date();
              date  Wyświetla:  
              
              date.toString();
              Wyświetla:  
              
              date.toTimeString();
              Wyświetla:  
              
              date.toLocaleTimeString();
              Wyświetla:  
              
              date.toUTCString();
              Wyświetla:  
              
              date.toDateString();
              Wyświetla:  
              
              date.toISOString();
              Wyświetla:  
              
              date.toLocaleString();
              Wyświetla:  
              
              date.getTime();
              Wyświetla: