Каждый фронтендер знает золотое правило оптимизации: «Тяжёлые вычисления — долой из основного потока!» Если нужно отфильтровать массив на сто тысяч товаров, распарсить массивный Excel-файл или обработать картинку в Canvas, мы создаём Web Worker и радостно перекладываем задачу в фоновый поток.
С точки зрения браузерного движка всё выглядит великолепно: главный поток (Main Thread) свободен, интерфейс не зависает, кадры скролла рендерятся с частотой 60 fps, а ключевая метрика отзывчивости INP (Interaction to Next Paint) в панелях мониторинга горит безупречно зелёным цветом.
Но что видит реальный человек перед экраном? Он нажимает на фильтр или генерирует отчёт и видит бесконечный спиннер. Страница формально не зависла, но приложение не отвечает, потому что цикл событий (event loop) самого воркера намертво встал под грузом расчётов. До недавнего времени у команд не было нативного способа собирать объективную RUM-телеметрию из воркеров: они оставались «чёрным ящиком».
Эту слепую зону закрывает Long Animation Frames API в Chrome 153, расширенный на контекст DedicatedWorkerGlobalScope.
Как запустить PerformanceObserver внутри Web Worker
Хотя в воркере нет понятий «отрисовка кадра» или перерисовки DOM, браузер переиспользует семантику спецификации LoAF для измерения длинных блокирующих задач в фоновом цикле событий.
Теперь прямо внутри файла скрипта воркера можно инициализировать стандартный наблюдатель производительности:
// telemetry-worker.js
if (typeof PerformanceObserver !== 'undefined' &&
PerformanceObserver.supportedEntryTypes.includes('long-animation-frame')) {
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// Пересылаем телеметрию блокировки в главный поток
self.postMessage({
type: 'PERF_TELEMETRY_LOAF',
payload: {
duration: entry.duration,
startTime: entry.startTime,
scripts: entry.scripts.map((script) => ({
invoker: script.invoker,
sourceURL: script.sourceURL,
executionDuration: script.executionDuration,
})),
},
});
}
});
observer.observe({ type: 'long-animation-frame', buffered: true });
}
А в основном потоке остаётся лишь слушать входящие сообщения и отправлять их в аналитическую систему:
// main.js
const worker = new Worker('/telemetry-worker.js');
worker.onmessage = (event) => {
if (event.data?.type === 'PERF_TELEMETRY_LOAF') {
const { duration, scripts } = event.data.payload;
console.warn(`[Worker LoAF] Воркер заблокирован на ${duration} мс:`, scripts);
// Отправка в RUM-систему (Datadog, Sentry или собственную аналитику)
}
};
Архитектурные нюансы и ограничения реализации
При проектировании мониторинга важно учитывать специфику рантайма:
- Изоляция контекстов: наблюдатель, созданный в
window, не видит события воркеров! Наблюдатель обязан запускаться именно внутри воркера. - Связка с кликом пользователя: чтобы понять, какой именно клик пользователя заблокировал воркер, передавай вместе с задачей сквозной
correlationIdи привязывай его к событиямPerformanceObserver. - Ограничение первой версии: как отмечает эксперт по производительности Гарри Робертс в блоге CSS Wizardry, текущая реализация ловит одну непрерывную долгую задачу (long task). Она пока не детектирует «пробку» из сотен быстрых микрозадач по 5 миллисекунд, которые суммарно создают задержку в несколько секунд.
Инструмент превращает Web Workers из тёмной комнаты в прозрачный конвейер. Если тебе интересна тема глубокой диагностики веб-приложений, рекомендую изучить наше практическое руководство по панели Performance в Chrome DevTools.
