Урок 10

Таймеры и планирование

setTimeout

Выполняет функцию один раз через указанное время:

setTimeout(() => console.log('позже'), 1000);

Возвращает ID для отмены:

const id = setTimeout(() => {}, 1000);
clearTimeout(id);

setInterval

Выполняет функцию регулярно:

const id = setInterval(() => console.log('тик'), 1000);
clearInterval(id);

Задержка не точная

setTimeout(fn, 100) — не «ровно через 100мс», а «не раньше». Задержка может быть больше из-за занятости event loop.

const start = Date.now();
setTimeout(() => console.log(Date.now() - start), 100);
// может быть 105, 110, 200...

setInterval и накопление

Если функция выполняется дольше интервала — вызовы накапливаются:

setInterval(() => {
    // если это занимает 2 секунды при интервале 1 секунда —
    // вызовы будут догонять
}, 1000);

Решение — setTimeout с рекурсией:

function tick() {
    doWork();
    setTimeout(tick, 1000);
}

tick();

Следующий вызов — через секунду после завершения текущего.

Асинхронный интервал

async function tick() {
    await doWork();
    setTimeout(tick, 1000);
}

tick();

Работает как setInterval, но не накапливает вызовы.

Delay через промис

const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

async function main() {
    console.log('начало');
    await sleep(1000);
    console.log('через секунду');
}

requestAnimationFrame

Вызывает функцию перед отрисовкой следующего кадра (обычно 60 раз в секунду):

function animate() {
    // обновление позиции
    element.style.transform = `translateX(${x}px)`;
    x += 1;

    if (x < 100) requestAnimationFrame(animate);
}

requestAnimationFrame(animate);

Синхронизируется с частотой монитора. Лучше для анимаций, чем setInterval.

Отмена анимации

let id = requestAnimationFrame(animate);
cancelAnimationFrame(id);

requestIdleCallback

Вызывает функцию, когда браузер свободен:

requestIdleCallback((deadline) => {
    while (deadline.timeRemaining() > 0 && tasks.length) {
        doTask(tasks.shift());
    }
}, { timeout: 2000 });

Не гарантирует вызов, но и не мешает важной работе.

Микро-таймеры

queueMicrotask — задача выполнится перед следующей макрозадачей:

queueMicrotask(() => console.log('микрозадача'));

Эквивалент Promise.resolve().then(...).

Debounce

function debounce(fn, delay) {
    let timer = null;

    return function(...args) {
        clearTimeout(timer);
        timer = setTimeout(() => fn.apply(this, args), delay);
    };
}

const search = debounce((q) => fetchResults(q), 300);

Throttle

function throttle(fn, delay) {
    let last = 0;

    return function(...args) {
        const now = Date.now();
        if (now - last >= delay) {
            last = now;
            fn.apply(this, args);
        }
    };
}

Расписание в Node.js

setImmediate(() => console.log('после текущей фазы'));
process.nextTick(() => console.log('до следующей микротаски'));

Порядок: синхронный код → process.nextTick → микрозадачи → макрозадачи.

Итоги

  • setTimeout — один раз
  • setInterval — регулярно, может накапливать
  • requestAnimationFrame — для анимаций
  • sleep через промис
  • debounce и throttle для событий
  • Микротаймеры для срочных задач