Spec-Zone.ru › Web APIs

API планирования задач с приоритетами

Примечание: Эта функция доступна в Потоках Web Workers.

API планирования задач с приоритетами предоставляет стандартный способ приоритизации всех задач приложения, независимо от того, определены ли они в коде разработчика сайта или в сторонних библиотеках и фреймворках.

Приоритеты задач являются грубыми и основаны на том, блокируют ли задачи взаимодействие пользователя или иным образом влияют на пользовательский опыт, или могут выполняться в фоновом режиме. Разработчики и фреймворки могут реализовывать более точные схемы приоритизации в рамках общих категорий, определенных API.

API основан на обещаниях и поддерживает возможность устанавливать и изменять приоритеты задач, откладывать добавление задач в планировщик, прерывать задачи и отслеживать события изменения приоритета и прерывания.

Концепции и использование

API планирования задач с приоритетами доступен как в окне, так и в рабочих потоках с использованием свойства scheduler глобального объекта.

Основные методы API — scheduler.postTask() и scheduler.yield(). scheduler.postTask() принимает функцию обратного вызова (задачу) и возвращает обещание, которое выполняется со значением возврата функции или отклоняется с ошибкой. scheduler.yield() превращает любую функцию async в задачу, передавая управление главному потоку браузера для выполнения других задач, и выполнение продолжается, когда возвращённое обещание выполняется.

Эти два метода имеют сходную функциональность, но разный уровень управления. scheduler.postTask() более настраиваемый — например, он позволяет явно задавать приоритет задачи и отменять задачу с помощью AbortSignal. scheduler.yield() проще и может быть использован в любой функции await без необходимости предоставления последующей задачи в другой функции.

scheduler.yield()

Чтобы разбить задачи JavaScript с длительным выполнением, чтобы они не блокировали основной поток, вставьте вызов scheduler.yield(), чтобы временно передать управление основному потоку браузера, что создаёт задачу для продолжения выполнения с того места, где она остановилась.

async function slowTask() {
  firstHalfOfWork();
  await scheduler.yield();
  secondHalfOfWork();
}

scheduler.yield() возвращает обещание, с помощью которого можно продолжить выполнение. Это позволяет включить работу, принадлежащую той же функции, без блокировки основного потока при выполнении функции.

scheduler.yield() не принимает аргументов. Задача, которая запускает её продолжение, имеет приоритет по умолчанию user-visible; однако, если scheduler.yield() вызывается внутри функции обратного вызова scheduler.postTask(), она будет наследовать приоритет окружающей задачи.

scheduler.postTask()

Когда scheduler.postTask() вызывается без аргументов, она создаёт задачу с приоритетом по умолчанию user-visible, которую нельзя прервать или изменить её приоритет.

const promise = scheduler.postTask(myTask);

Поскольку метод возвращает обещание, вы можете дождаться его выполнения асинхронно, используя then(), и перехватить ошибки, брошенные функцией обратного вызова задачи (или при прерывании задачи), используя catch. Функция обратного вызова может быть любой функцией (ниже мы демонстрируем стрелочную функцию).

scheduler
  .postTask(() => "Task executing")
  // Promise resolved: log task result when promise resolves
  .then((taskResult) => console.log(`${taskResult}`))
  // Promise rejected: log AbortError or errors thrown by task
  .catch((error) => console.error(`Error: ${error}`));

Ту же задачу можно ожидать, используя await/async, как показано ниже (обратите внимание, что это выполняется в выражении функции, вызываемом сразу (IIFE)):

(async () => {
  try {
    const result = await scheduler.postTask(() => "Task executing");
    console.log(result);
  } catch (error) {
    // Log AbortError or error thrown in task function
    console.error(`Error: ${error}`);
  }
})();

Вы также можете указать объект параметров методу postTask(), если хотите изменить поведение по умолчанию. Параметры:

  • priority Это позволяет указать конкретный неизменяемый приоритет. После установки приоритет изменить нельзя.
  • signal Это позволяет указать сигнал, который может быть либо TaskSignal, либо AbortSignal. Сигнал связан с контроллером, который можно использовать для прерывания задачи. TaskSignal также может использоваться для установки и изменения приоритета задачи, если задача является изменяемой.
  • delay Это позволяет указать задержку перед добавлением задачи в планировщик в миллисекундах.

Тот же пример, что и выше, с параметром приоритета:

scheduler
  .postTask(() => "Task executing", { priority: "user-blocking" })
  .then((taskResult) => console.log(`${taskResult}`)) // Log the task result
  .catch((error) => console.error(`Error: ${error}`)); // Log any errors

Приоритеты задач

Задачи, запланированные на выполнение, запускаются в порядке приоритета, а затем в порядке их добавления в очередь планировщика.

Существует всего три приоритета, перечисленные ниже (от наивысшего к наименьшему):

user-blocking

Задачи, которые препятствуют взаимодействию пользователя со страницей. Это включает в себя отрисовку страницы до точки, где она может быть использована, или реакцию на действия пользователя.

user-visible

Задачи, которые видны пользователю, но не обязательно блокируют действия пользователя. Это может включать отрисовку некритичных частей страницы, таких как некритические изображения или анимации.

Это приоритет по умолчанию для scheduler.postTask() и scheduler.yield().

background

Задачи, которые не являются критичными по времени. Это может включать обработку журналов или инициализацию сторонних библиотек, которые не требуются для отрисовки.

Изменяемый и неизменяемый приоритет задачи

Существует много случаев, когда приоритет задачи не нужно изменять, а в других — нужно. Например, загрузка изображения может измениться от задачи background к задаче user-visible при прокрутке карусели в область просмотра.

Приоритет задачи может быть статическим (неизменяемым) или динамическим (изменяемым) в зависимости от аргументов, переданных в Scheduler.postTask().

Приоритет задачи неизменяемый, если значение указано в аргументе options.priority. Указанное значение будет использоваться для приоритета задачи и изменить его нельзя.

Приоритет изменяемый только если TaskSignal передан в аргумент options.signal и options.priority не задан. В этом случае задача будет использовать свой начальный приоритет из signal приоритета, а приоритет можно впоследствии изменить, вызвав TaskController.setPriority() для контроллера, связанного с сигналом.

Если приоритет не задан с options.priority или путём передачи TaskSignal в options.signal, он по умолчанию будет user-visible (и по определению неизменяемым).

Обратите внимание, что задача, которая должна быть прервана, должна установить options.signal либо в TaskSignal, либо в AbortSignal. Однако для задачи с неизменяемым приоритетом, AbortSignal более чётко указывает, что приоритет задачи изменить нельзя с помощью сигнала.

Давайте рассмотрим пример, чтобы продемонстрировать, что мы имеем в виду. Когда у вас несколько задач с примерно одинаковым приоритетом, имеет смысл разбить их на отдельные функции для улучшения поддержки, отладки и многих других причин.

Например:

function main() {
  a();
  b();
  c();
  d();
  e();
}

Однако такая структура не помогает при блокировке основного потока. Поскольку все пять задач выполняются внутри одной главной функции, браузер выполняет их все как одну задачу.

Для решения этой проблемы мы периодически запускаем функцию, чтобы заставить код передать управление главному потоку. Это означает, что наш код разделен на несколько задач, между выполнением которых браузеру предоставляется возможность обрабатывать задачи высокого приоритета, такие как обновление пользовательского интерфейса. Общий шаблон для этой функции использует setTimeout() для отсрочки выполнения в отдельной задаче:

function yield() {
  return new Promise((resolve) => {
    setTimeout(resolve, 0);
  });
}

Это можно использовать в схеме планировщика задач, подобной этой, чтобы передавать управление главному потоку после выполнения каждой задачи:

async function main() {
  // Create an array of functions to run
  const tasks = [a, b, c, d, e];

  // Loop over the tasks
  while (tasks.length > 0) {
    // Shift the first task off the tasks array
    const task = tasks.shift();

    // Run the task
    task();

    // Yield to the main thread
    await yield();
  }
}

Для дальнейшего улучшения можно использовать Scheduler.yield, когда это возможно, чтобы позволить этому коду продолжить выполнение перед другими менее критическими задачами в очереди:

function yield() {
  // Use scheduler.yield if it exists:
  if ("scheduler" in window && "yield" in scheduler) {
    return scheduler.yield();
  }

  // Fall back to setTimeout:
  return new Promise((resolve) => {
    setTimeout(resolve, 0);
  });
}

Интерфейсы

Scheduler

Содержит методы postTask() и yield() для добавления задач с приоритетами, которые нужно запланировать. Экземпляр этого интерфейса доступен в Window или WorkerGlobalScope глобальных объектах (globalThis.scheduler).

TaskController

Поддерживает как прерывание задачи, так и изменение её приоритета.

TaskSignal

Объект сигнала, который позволяет прервать задачу и изменить её приоритет, если это необходимо, с помощью объекта TaskController.

TaskPriorityChangeEvent

Интерфейс для события prioritychange, которое отправляется при изменении приоритета задачи.

Примечание: Если приоритет задачи никогда не нужно изменять, можно использовать AbortController и связанный с ним AbortSignal вместо TaskController и TaskSignal.

Расширения других интерфейсов

Window.scheduler и WorkerGlobalScope.scheduler

Эти свойства являются точками входа для использования метода Scheduler.postTask() в окне или в области видимости рабочего потока соответственно.

Примеры

Обратите внимание, что в примерах ниже используется myLog() для записи в текстовую область. Код для области лога и метода обычно скрыт, чтобы не отвлекать от более релевантного кода.

// hidden logger code - simplifies example
let log = document.getElementById("log");
function myLog(text) {
  log.textContent += `${text}\n`;
}

Проверка наличия функции

Проверьте, поддерживается ли планирование задач с приоритетами, проверив свойство scheduler в глобальной области видимости.

Приведённый ниже код выводит "Функция: Поддерживается", если API поддерживается в этом браузере.

// Check that feature is supported
if ("scheduler" in globalThis) {
  myLog("Feature: Supported");
} else {
  myLog("Feature: NOT Supported");
}

Базовое использование

Задачи публикуются с помощью Scheduler.postTask(), указанием функции обратного вызова (задачи) в первом аргументе и необязательным вторым аргументом, который может использоваться для указания приоритета задачи, сигнала и/или задержки. Метод возвращает Promise, который разрешается значением возвращаемым функцией обратного вызова или отклоняется с ошибкой прерывания или ошибкой, выброшенной в функции.

Поскольку он возвращает промис, Scheduler.postTask() может быть присоединён к другим промисам. Ниже мы показываем, как дождаться разрешения промиса с помощью then. Это использует приоритет по умолчанию (user-visible).

// A function that defines a task
function myTask() {
  return "Task 1: user-visible";
}

if ("scheduler" in this) {
  // Post task with default priority: 'user-visible' (no other options)
  // When the task resolves, Promise.then() logs the result.
  scheduler.postTask(myTask).then((taskResult) => myLog(`${taskResult}`));
}

Метод также может быть использован с await внутри асинхронной функции. Ниже показано, как можно использовать этот подход, чтобы дождаться завершения user-blocking задачи.

function myTask2() {
  return "Task 2: user-blocking";
}

async function runTask2() {
  const result = await scheduler.postTask(myTask2, {
    priority: "user-blocking",
  });
  myLog(result); // Logs 'Task 2: user-blocking'.
}
runTask2();

В некоторых случаях вам может не потребоваться ожидание завершения. Для простоты во многих примерах здесь просто регистрируется результат по мере выполнения задачи.

// A function that defines a task
function myTask3() {
  myLog("Task 3: user-visible");
}

if ("scheduler" in this) {
  // Post task and log result when it runs
  scheduler.postTask(myTask3);
}

Лог ниже отображает вывод трёх задач выше. Обратите внимание, что порядок их выполнения зависит от приоритета, а затем от порядка объявления.

Постоянные приоритеты

Приоритеты задач могут быть установлены с помощью параметра priority в необязательном втором аргументе. Приоритеты, установленные таким образом, являются неизменяемыми (не могут быть изменены).

Ниже мы публикуем две группы из трёх задач, каждый член в обратном порядке приоритета. Последняя задача имеет приоритет по умолчанию. При запуске каждая задача просто регистрирует свой ожидаемый порядок (мы не ждём результата, потому что нам это не нужно, чтобы показать порядок выполнения).

if ("scheduler" in this) {
  // three tasks, in reverse order of priority
  scheduler.postTask(() => myLog("bkg 1"), { priority: "background" });
  scheduler.postTask(() => myLog("usr-vis 1"), { priority: "user-visible" });
  scheduler.postTask(() => myLog("usr-blk 1"), { priority: "user-blocking" });

  // three more tasks, in reverse order of priority
  scheduler.postTask(() => myLog("bkg 2"), { priority: "background" });
  scheduler.postTask(() => myLog("usr-vis 2"), { priority: "user-visible" });
  scheduler.postTask(() => myLog("usr-blk 2"), { priority: "user-blocking" });

  // Task with default priority: user-visible
  scheduler.postTask(() => myLog("usr-vis 3 (default)"));
}

Вывод ниже показывает, что задачи выполняются в порядке приоритета, а затем в порядке объявления.

Изменение приоритетов задач

Приоритеты задач также могут принимать начальное значение от TaskSignal, переданного в postTask() в необязательном втором аргументе. Если они установлены таким образом, приоритет задачи может быть изменён с помощью контроллера, связанного с сигналом.

Примечание: Установка и изменение приоритетов задач с использованием сигнала работает только тогда, когда аргумент options.priority для postTask() не задан и когда options.signal является TaskSignal (а не AbortSignal).

Код ниже сначала показывает, как создать TaskController, установив начальный приоритет сигнала до user-blocking в конструкторе TaskController().

Затем код использует addEventListener() для добавления обработчика событий к сигналу контроллера (можно использовать свойство TaskSignal.onprioritychange для добавления обработчика событий). Обработчик событий использует previousPriority в событии для получения исходного приоритета и TaskSignal.priority в целевом объекте события для получения нового/текущего приоритета.

Затем задача публикуется, передавая сигнал, и сразу же приоритет изменяется до background вызовом TaskController.setPriority() на контроллере.

if ("scheduler" in this) {
  // Create a TaskController, setting its signal priority to 'user-blocking'
  const controller = new TaskController({ priority: "user-blocking" });

  // Listen for 'prioritychange' events on the controller's signal.
  controller.signal.addEventListener("prioritychange", (event) => {
    const previousPriority = event.previousPriority;
    const newPriority = event.target.priority;
    myLog(`Priority changed from ${previousPriority} to ${newPriority}.`);
  });

  // Post task using the controller's signal.
  // The signal priority sets the initial priority of the task
  scheduler.postTask(() => myLog("Task 1"), { signal: controller.signal });

  // Change the priority to 'background' using the controller
  controller.setPriority("background");
}

Вывод ниже демонстрирует, что приоритет был успешно изменён на background с user-blocking. Обратите внимание, что в этом случае приоритет изменяется до выполнения задачи, но его также можно было изменить во время выполнения задачи.

Прерывание задач

Задачи могут быть прерваны с помощью либо TaskController, либо AbortController точно так же. Единственное отличие состоит в том, что необходимо использовать TaskController, если вы также хотите установить приоритет задачи.

Код ниже создаёт контроллер и передаёт его сигнал в задачу. Затем задача немедленно прерывается. Это приводит к отклонению промиса с AbortError, что перехватывается в блоке catch и регистрируется. Обратите внимание, что мы также могли бы прослушать событие abort, сгенерированное по TaskSignal или AbortSignal и записать прерывание там.

if ("scheduler" in this) {
  // Declare a TaskController with default priority
  const abortTaskController = new TaskController();
  // Post task passing the controller's signal
  scheduler
    .postTask(() => myLog("Task executing"), {
      signal: abortTaskController.signal,
    })
    .then((taskResult) => myLog(`${taskResult}`)) // This won't run!
    .catch((error) => myLog(`Error: ${error}`)); // Log the error

  // Abort the task
  abortTaskController.abort();
}

Лог ниже отображает прерванную задачу.

Задержка задач

Задачи могут быть отложены, указав целое число миллисекунд в параметре options.delay для postTask(). Это фактически добавляет задачу в очередь с приоритетами по таймауту, как это может быть создано с помощью setTimeout(). delay — это минимальное время, прошедшее до того, как задача будет добавлена в планировщик; оно может быть больше.

Код ниже показывает две добавленные задачи (как стрелочные функции) с задержкой.

if ("scheduler" in this) {
  // Post task as arrow function with delay of 2 seconds
  scheduler
    .postTask(() => "Task delayed by 2000ms", { delay: 2000 })
    .then((taskResult) => myLog(`${taskResult}`));
  scheduler
    .postTask(() => "Next task should complete in about 2000ms", { delay: 1 })
    .then((taskResult) => myLog(`${taskResult}`));
}

Обновите страницу. Обратите внимание, что вторая строка появляется в логе примерно через 2 секунды.

Спецификации

Спецификация
Планирование задач с приоритетами
# scheduler
Раннее обнаружение событий ввода
# the-scheduling-interface

Совместимость с браузерами

Рабочий стол Мобильный
Chrome Edge Firefox Opera Safari Chrome Android Firefox для Android Opera Android Safari на iOS Samsung Internet WebView Android
Prioritized_Task_Scheduling_API 87 87 Нет 73 Нет 87 Нет 62 Нет 14.0 87
isInputPending 87 87 Нет 73 Нет 87 Нет 62 Нет 14.0 87
END_OF_DOCUMENT_MARKER
Десктоп Мобильное
Chrome Edge Firefox Opera Safari Chrome Android Firefox for Android Opera Android Safari on IOS Samsung Internet WebView Android
Prioritized_Task_Scheduling_API 94 94 101 80 Нет 94 Нет 66 Нет 17.0 94
postTask 94 94 101 80 Нет 94 Нет 66 Нет 17.0 94
yield 129 129 Нет 115 Нет 129 Нет 86 Нет Нет 129

api.Scheduler

Таблицы BCD загружаются только в браузере

api.Scheduling

Таблицы BCD загружаются только в браузере

См. также

  • Создание более быстрого веб-опыта с планировщиком postTask в блоге Airbnb (2021)
  • Оптимизация длительных задач на web.dev (2022)

© 2005–2024 MDN contributors.
Licensed under the Creative Commons Attribution-ShareAlike License v2.5 or later.
https://developer.mozilla.org/en-US/docs/Web/API/Prioritized_Task_Scheduling_API

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API