Timeout i interwały

Gdy odpalimy nasz skrypt, wykonuje się on natychmiast - linia po linii.

W JavaScript istnieją też asynchroniczne funkcje, które pozwalają odpalić nasz kod z opóźnieniem czasowym, lub pozwalają odpalać taki kod cyklicznie co jakiś czas.

Są to kolejno:

setTimeout(fn, time*, par1*, par2*...) odpala przekazaną funkcję po jakimś czasie
setInterval(fn, time*, par1*, par2*...) pozwala odpalić zadaną funkcję cyklicznie co jakiś czas
requestAnimationFrame(fn) odpala funkcję przed kolejnym rysowaniem naszej strony. Stosowana głównie przy animacjach, dzięki czemu mamy pewność, że dany kod będzie odpalany możliwie płynnie

setTimeout

Pierwszą z takich funkcji jest setTimeout(fn, time*, arg1*, arg2*...) - przydatna w przypadkach gdy np. chcemy po jakimś czasie pokazać jakiś modal, ukryć coś itp.
W pierwszym parametrze przekazujemy funkcję, która zostanie odpalana po zadanym czasie. W drugim parametrze podajemy czas w milisekundach - domyślnie wynosi on 0. Czas ten oznacza czas, po jakim zostanie odpalona nasza funkcja.


function myFunc() {
    console.log("Jakiś tekst");
}

setTimeout(myFunc, 1200); //odpali po 1.2s

setTimeout(() => {
    console.log("z zaskoczenia!");
}, 3000);

Przekazywany w drugim parametrze czas jest czasem przybliżonym. To, że podamy tutaj np. 10, nie oznacza, że nasza funkcja zostanie odpalona idealnie za dziesięć milisekund. Czemu tak się dzieje - dowiesz się poniżej.

Żeby przerwać wcześniej zainicjowany setTimeout (ale przed jego wykonaniem) korzystamy z metody clearTimeout() która w parametrze przyjmuje zmienną, pod którą zostało wcześniej podstawione wywołanie setTimeout:


const time = setTimeout(() => {
    console.log("Pełne zaskoczenie");
}, 10000);

clearTimeout(time); //powyższa funkcja nigdy się nie odpali, bo od razu przerwaliśmy setTimeout

Jeżeli przekazana do setTimeout funkcja wymaga przekazania atrybutów, możemy je podać jako kolejne wartości dla setTimeout:


function print(txt, nr) {
    console.log(txt, nr);
}

//wraz z przekazaną referencja do funkcji
setTimeout(print, 2000, "Ala ma kota", 102);

//wraz z funkcją anonimową
setTimeout((txt, nr) => {
    console.log(txt, nr);
}, 2000, "Ala ma kota", 102);

setInterval

Funkcja setInterval(fn, time*, arg1*, arg2*...) działa podobnie do setTimeout, jednak w przeciwieństwie do powyższej przekazana funkcja będzie odpalana cyklicznie, aż do zatrzymania lub opuszczenia strony przez użytkownika:


setInterval(() => {
    console.log("Przykładowy napis");
}, 1000);

Żeby zatrzymać odpalony interwał, skorzystamy z metody clearInterval(), która podobnie do clearTimeout przyjmuje tylko jeden parametr, który jest zmienną, pod która wcześniej zostało podstawione zadeklarowanie setInterval:


const interval = setInterval(() => {
    console.log("Wypiszę się co 1 sekundę");
}, 1000);

clearInterval(interval);

Przerwanie interwału możemy też wykonywać z jego wnętrza:


let i = 0;
const interval = setInterval(() => {
    i++;
    console.log(i);
    if (i >= 10) {
        clearInterval(interval);
    }
}, 1000);

Oczywiście nic nie stoi na przeszkodzie, by łączyć omawiane funkcje:


let i = 0;
const interval = setInterval(() => {
    document.body.textContent = ++i;
}, 1000 / 30) //30 klatek na sekundę

//po 5 sekundach przerywam wypisywanie
setTimeout(() => {
    clearInterval(interval);
}, 5000);

requestAnimationFrame

Funkcja requestAnimationFrame(fn) pozwala odpalić przekazaną do niej funkcję przed kolejnym rysowaniem (patrz poniżej). Funkcja ta stosowana jest głównie w przypadku wszelakich animacji.


let element = document.querySelector(".element");

function animate() {
    const left = parseInt(element.style.left);
    element.style.left = left + "px";

    requestAnimationFrame(animate);
}

animate();

Więcej na temat jej użycia dowiesz się w kolejnym rozdziale. My głównie będziemy jej używać w rozdziale o animacjach w canvas.

setInterval nie zawsze dobry

Funkcja setInterval wywołuje naszą funkcję co zadany czas, nie patrząc na to, czy jej kod zdąży się wykonać czy nie. Mogą to być liczne operacje na danych, czy też złożone operacje na elementach strony. Dość często nie będziemy wiedzieli ile dana funkcja będzie się wykonywać. W takim przypadku wykonywanie naszej funkcji może zacząć się nakładać:


function longFunction() {
    setInterval(() => {
        for (let i=0; i

Gdy odpalisz ten kod w konsoli, zauważysz, że liczby są wypisywane w złej kolejności. Poprzednia pętla nie zdąży się wykonać, a już odpalane jest kolejne powtórzenie interwału.

Jeżeli zakładamy, że wykonanie kodu naszej funkcji może zająć więcej niż odstępy interwału, prawdopodobnie lepszym wyborem będzie zastosowanie setTimeout, który będzie w danej funkcji wywoływał daną funkcję:


function longFunction() {
    for (let i=0; i

Debounce i throttle

Wyobraź sobie, że robisz dynamiczną wyszukiwarkę na stronie. Użytkownik wpisuje litery do pola, a ty co litera wykonujesz za pomocą Ajax do serwera zapytanie, a następnie zwracasz użytkownikowi wyniki.


//kod teoretyczny
input.addEventListener("input", e => {
    $.get("....", function(result) {
        console.log(result);
    })
});

Problem z takim podejściem jest taki, że większość użytkowników internetu szybko pisze, więc nasze kolejne zapytania będą się bardzo często wykonywać. Zbyt często. Dość szybko spowoduje to przyblokowanie kolejnych zapytań.

Żeby ci to zademonstrować, zróbmy sytuację za pomocą setTimeout, które zastąpi nam dynamiczne połączenie:


input.addEventListener("input", e => {
    const text = input.value.repeat(10);
    setTimeout(() => {
        console.log(`Wyszukiwana fraza to: ${text}`);
    }, 3000);
});

Ale nie tylko dynamicznych wyszukiwarek się to tyczy. Każda sytuacja, która potencjalnie może odpalać nasz kod zbyt szybko będzie potencjalnie problematyczna.

Tutaj podobny problem - tym razem tyczy się zmiany rozmiaru okna strony (event resize), który omawialiśmy tutaj.

Właśnie w takich i podobnych sytuacjach stosuje się omawiane techniki.

Pierwsza z nich nosi nazwę throttle.


function throttled(delay, fn) {
    let lastCall = 0;
    return function (...args) {
        const now = (new Date).getTime();
        if (now - lastCall 

const tHandler = throttled(200, printKey);
const input = document.querySelector("input");
input.addEventListener("input", tHandler);

Druga z nich nosi nazwę debounce. Najczęściej używaną implementacją debounce jest chyba ta zastosowana w bibliotece lodash, ale możliwych implementacji jest bardzo dużo (np. dla jQuery). Poniżej zamieszczam inną przykładową implementację:


function debounced(delay, fn) {
    let timerId;
    return function(...args) {
        if (timerId) {
            clearTimeout(timerId);
        }
        timerId = setTimeout(() => {
            fn(...args);
            timerId = null;
        }, delay);
    }
}

const tHandler = debounced(200, printKey);
const input = document.querySelector("input");
input.addEventListener("input", tHandler);

Pokazany wcześniej przykład z problematyczną zmianą rozmiaru okna wraz z zaimplementowaną funkcją debounce

Wszelkie prawa zastrzeżone. Jeżeli chcesz używać jakiejś części tego kursu, skontaktuj się z autorem.
Aha - i ta strona korzysta z ciasteczek.