Браузер даёт 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.