Урок по треку Браузер и DOM 7 мин

Web API и воркеры без мистики

Web Worker, Service Worker, IntersectionObserver, ResizeObserver, fetch с AbortController.

Браузер даёт JavaScript десятки API сверх самого языка. На собесе чаще всего спрашивают про воркеры, наблюдатели (observers) и отмену запросов. Разберём их по очереди.

Главный поток и зачем нужны воркеры

JavaScript страницы выполняется в одном главном потоке. В нём же браузер считает стили, раскладку и обрабатывает клики. Если ваш код считает что-то 300 мс, страница на 300 мс «замерзает»: клики не обрабатываются, анимации на JS стоят.

Web Worker запускает скрипт в отдельном потоке:

// main.js
const worker = new Worker(new URL('./heavy.js', import.meta.url), { type: 'module' });
worker.postMessage({ numbers: bigArray });
worker.onmessage = (e) => console.log('готово', e.data);

// heavy.js
self.onmessage = (e) => {
  const sum = e.data.numbers.reduce((a, b) => a + b, 0);
  self.postMessage(sum);
};

Что важно знать:

  • У воркера нет доступа к DOM и к window. Есть fetch, таймеры, IndexedDB, WebSocket.
  • Общение только через postMessage. Данные копируются алгоритмом structured clone (функции и DOM-узлы передать нельзя).
  • Большой ArrayBuffer можно не копировать, а передать (transfer): postMessage(buf, [buf]). После этого в отправителе буфер становится пустым.
  • SharedWorker один на несколько вкладок одного origin, SharedArrayBuffer требует cross-origin isolation (заголовки COOP и COEP).

Service Worker

Service Worker это воркер-посредник между страницей и сетью. Он перехватывает запросы своего scope через событие fetch и может отвечать из кеша. На нём держатся офлайн-режим, PWA и push-уведомления.

// регистрация на странице
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js');
}

// sw.js
self.addEventListener('install', (e) => {
  e.waitUntil(caches.open('v1').then((c) => c.addAll(['/', '/app.css'])));
});
self.addEventListener('fetch', (e) => {
  e.respondWith(
    caches.match(e.request).then((hit) => hit ?? fetch(e.request))
  );
});

Ключевые факты:

  • Работает только по HTTPS (исключение: localhost).
  • Жизненный цикл: install → waiting → activate. Новая версия ждёт, пока закроются все вкладки со старой, если не вызвать self.skipWaiting(). Чтобы взять под контроль уже открытые страницы, в activate вызывают clients.claim().
  • Scope по умолчанию это папка, где лежит файл SW. /js/sw.js не перехватит запросы страницы /.
  • Браузер может остановить SW в любой момент простоя, поэтому глобальные переменные в нём не надёжны.
  • Стратегии кеширования: cache first (статика), network first (API, свежесть важнее), stale-while-revalidate (отдать из кеша и обновить в фоне).

Наблюдатели вместо обработчиков scroll и resize

IntersectionObserver сообщает, когда элемент входит в область видимости или выходит из неё. Это ленивые картинки, бесконечная лента, аналитика показов. Срабатывает асинхронно и не заставляет вас вызывать getBoundingClientRect на каждый scroll.

const io = new IntersectionObserver((entries) => {
  for (const e of entries) {
    if (e.isIntersecting) {
      loadMore();
      io.unobserve(e.target);
    }
  }
}, { rootMargin: '200px' });
io.observe(document.querySelector('#sentinel'));

ResizeObserver сообщает об изменении размеров конкретного элемента (не окна). MutationObserver сообщает об изменениях DOM: добавлении узлов, атрибутов, текста. Колбэк MutationObserver выполняется как микрозадача, пачкой записей.

fetch и отмена

fetch возвращает промис, который отклоняется только при сетевой ошибке или отмене. Ответ 404 или 500 это успешный промис с response.ok === false.

const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 5000);
try {
  const res = await fetch('/api/items', { signal: controller.signal });
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  const data = await res.json();
} catch (err) {
  if (err.name === 'AbortError') console.log('отменили');
} finally {
  clearTimeout(timer);
}

Для таймаута есть готовое AbortSignal.timeout(5000) (ошибка TimeoutError). Один сигнал можно отдать нескольким запросам и отменить их разом.

Ещё полезное

  • requestAnimationFrame(cb) вызывает колбэк перед следующей отрисовкой кадра. Для анимаций на JS лучше, чем setInterval: синхронизирован с частотой экрана и останавливается в фоновой вкладке.
  • requestIdleCallback выполняет работу, когда браузеру нечем заняться. В Safari его до сих пор нет по умолчанию, нужен запасной вариант.
  • BroadcastChannel передаёт сообщения между вкладками одного origin.
  • navigator.sendBeacon(url, data) надёжно отправляет аналитику при уходе со страницы.

Частые ошибки

  • Пытаться обратиться к document из воркера.
  • Считать, что fetch бросит исключение на 404.
  • Положить sw.js в подпапку и удивляться, что он не перехватывает запросы корня.
  • Вешать тяжёлую логику на scroll вместо IntersectionObserver.
  • Забыть waitUntil в install: SW установится до того, как кеш наполнится.

Что спросят на собесе

  • Зачем нужен Web Worker и чего в нём нельзя делать.
  • Чем Service Worker отличается от обычного воркера и как устроен его жизненный цикл.
  • Как сделать ленивую загрузку или бесконечную ленту без обработчика scroll.
  • Как отменить fetch и почему 404 не попадает в catch.
  • Какие стратегии кеширования бывают в Service Worker.

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.