Работа с JavaScript Scheduler API
JavaScript Scheduler API представляет стандартизированный подход к управлению приоритетами задач в веб-приложениях.
Разработчики JavaScript исторически полагались на setTimeout(0)
для передачи главного потока. Новый API обеспечивает более точный контроль над тем, как и когда выполняются задачи.
Традиционному подходу setTimeout
к передаче главного потока не хватает детального контроля и правильной расстановки приоритетов. API Scheduler устраняет эти недостатки, предлагая более надёжное решение.
// Старый подход с setTimeout
setTimeout(() => {
processNextBatch();
}, 0);
Scheduler API предлагает два основных метода для лучшего контроля: yield()
и postTask()
. Более простой метод yield()
обеспечивает чистый способ приостановить выполнение.
// Новый подход с scheduler.yield()
await scheduler.yield();
processNextBatch();
Для более сложных сценариев функция postTask()
позволяет планировать задачи на основе приоритетов:
await scheduler.postTask(() => {
processNextBatch() },
{ priority: 'background' }
);
Уровни приоритета задач
API планировщика поддерживает три уровня приоритета для задач: user-blocking
, user-visible
и background
. Задачи выполняются в порядке приоритета, то есть в той последовательности, в которой они были добавлены в очередь планировщика.
// TypeScript
enum TaskPriority {
"user-blocking",
"user-visible",
"background"
};
Уровень приоритета определяет, когда и насколько срочно браузер должен выполнить задачу. Вот что означает каждый из приоритетов:
user-blocking
: Задачи, блокирующие основной поток и требующие немедленного внимания. Например, обработка пользовательского ввода или обновление пользовательского интерфейса.user-visible
: Важные, но не такие срочные задачи, какuser-blocking
. Например, получение данных для следующего экрана.background
: Задачи, выполняемые в фоновом режиме и не влияющие на работу пользователя. Например, предварительная загрузка ресурсов или выполнение аналитики.
Управление несколькими задачами с приоритетами
Scheduler API отлично справляется с управлением несколькими задачами с разными приоритетами, обеспечивая выполнение критически важных операций в первую очередь и откладывая менее важную работу. Когда несколько задач выполняются одновременно, браузер сначала выполняет их в порядке приоритета, а затем в порядке очереди.
async function processData(data) {
const controller = new TaskController({ priority: 'background' });
try {
await scheduler.postTask(
() => heavyComputation(data),
{
signal: controller.signal,
priority: 'background'
}
);
} catch (error) {
if (error.name !== 'AbortError') {
console.error('Processing failed:', error);
}
}
}
Динамическое изменение приоритета
Задачи могут менять приоритет во время выполнения используя TaskController
. Это ценно, когда важность задачи меняется в зависимости от взаимодействия с пользователем или состояния системы.
const controller = new TaskController({ priority: 'background' });
controller.signal.addEventListener('prioritychange', (event) => {
console.log(`Priority changed from ${event.previousPriority} to ${event.target.priority}`);
});
scheduler.postTask(
async () => {
// Запуск задачи как `background`
await processInitialData();
// Повышение приоритета при необходимости
controller.setPriority('user-visible');
await processCriticalData();
},
{ signal: controller.signal }
);
Прерывание задач
Scheduler API позволяет прерывать задачи как через TaskController
, так и через AbortController
// Базовое прерывание задачи
const controller = new TaskController();
scheduler.postTask(
async () => {
const response = await fetch('https://api.example.com/data');
return response.json();
},
{ signal: controller.signal }
).catch(error => {
if (error.name === 'AbortError') {
console.log('Task was aborted');
}
});
// Прерывание задачи
controller.abort();
Поддержка браузерами и резервный паттерн
Прежде чем использовать Scheduler API, проверьте, поддерживает ли его браузер. Создайте обёртку, при необходимости возвращающуюся к setTimeout
.
function createScheduler() {
if ('scheduler' in window) {
return {
async schedule(task, options = {}) {
return scheduler.postTask(task, options);
},
async yield() {
return scheduler.yield();
}
};
}
return {
async schedule(task, { delay = 0 } = {}) {
return new Promise((resolve, reject) => {
setTimeout(() => {
try {
resolve(task());
} catch (error) {
reject(error);
}
}, delay);
});
},
async yield() {
return new Promise(resolve => setTimeout(resolve, 0));
}
};
}
/*
Используйте этот запасной шаблон, чтобы обеспечить работу приложения
во всех браузерах, используя преимущества Scheduler API, где это возможно.
*/
const taskScheduler = createScheduler();
async function processData(data) {
try {
await taskScheduler.schedule(
() => heavyComputation(data),
{ priority: 'background' }
);
} catch (error) {
console.error('Processing failed:', error);
}
}
Scheduler API продолжает эволюционировать, а метод yield()
момент написания статьи () помечен как экспериментальный. Поддержка браузеров постоянно развивается, и в основных браузерах реализована ключевая функциональность. Дизайн API позволяет расширять его в будущем, сохраняя обратную совместимость.
Этот стандартизированный подход к планированию задач представляет значительный шаг вперёд для производительности веб-приложений. Он позволяет создавать более отзывчивые интерфейсы и лучше использовать ресурсы, не прибегая к специфичным для конкретной реализации хакам или обходным путям.
Scheduler API решает фундаментальные проблемы производительности веб-приложений. Его чёткая модель приоритетов и возможность прерывания обеспечивают инструменты, необходимые для создания более отзывчивых приложений. По мере расширения поддержки браузеров этот API станет неотъемлемой частью набора инструментов веб-разработчика.