Используйте Scheduler.yield() для разбиения длинных задач

Опубликовано: 6 марта 2025 г.

Browser Support

  • Хром: 129.
  • Край: 129.
  • Firefox: не поддерживается.
  • Сафари: не поддерживается.

Source

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

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

scheduler.yield предлагает эргономичный API, который делает именно то, что говорит: выполнение вызванной функции приостанавливается в выражении await scheduler.yield() и переходит к основному потоку, разбивая задачу. Выполнение оставшейся части функции, называемой продолжением функции, будет запланировано в новой задаче цикла событий.

async function respondToUserClick() {
  giveImmediateFeedback();
  await scheduler.yield(); // Yield to the main thread.
  slowerComputation();
}

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

Такие функции, как setTimeout или scheduler.postTask , также можно использовать для разделения задач, но эти продолжения обычно запускаются после любых новых задач, уже поставленных в очередь, потенциально создавая риск длительных задержек между переходом к основному потоку и завершением своей работы.

Приоритетное продолжение после уступки

scheduler.yield является частью API планирования приоритетных задач . Как веб-разработчики, мы обычно не говорим о порядке, в котором цикл событий выполняет задачи с точки зрения явных приоритетов, но относительные приоритеты всегда присутствуют , например, обратный вызов requestIdleCallback , выполняемый после любых поставленных в очередь обратных вызовов setTimeout , или триггерный прослушиватель событий ввода, обычно запускаемый перед тем, как задача поставлена ​​в очередь с помощью setTimeout(callback, 0) .

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

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

Вот пример: две функции, поставленные в очередь для выполнения разных задач с использованием setTimeout .

setTimeout(myJob);
setTimeout(someoneElsesJob);

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

Вот как эта работа может выглядеть в DevTools:

Две задачи показаны на панели производительности Chrome DevTools. Обе задачи обозначены как длинные: функция «myJob» берет на себя все выполнение первой задачи, а функция «someoneElsesJob» берет на себя всю работу второй задачи.

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

function myJob() {
  // Run part 1.
  myJobPart1();
  // Yield with setTimeout() to break up long task, then run part2.
  setTimeout(myJobPart2, 0);
}

Поскольку запуск myJobPart2 был запланирован с помощью setTimeout внутри myJob , но это планирование запускается после того, как someoneElsesJob уже запланирован, вот как будет выглядеть выполнение:

Три задачи, показанные на панели производительности Chrome DevTools. Первая задача запускает функцию «myJobPart1», вторая — длинная задача, выполняющая «someoneElsesJob», и, наконец, третья задача запускает «myJobPart2».

Мы разбили задачу с помощью setTimeout , чтобы браузер мог реагировать в середине myJob , но теперь вторая часть myJob запускается только после того, как someoneElsesJob завершит работу.

В некоторых случаях это может быть хорошо, но обычно это не оптимально. myJob уступал основной поток, чтобы страница могла реагировать на ввод пользователя, а не полностью отказываться от основного потока. В тех случаях, когда someoneElsesJob работает особенно медленно или запланировано много других заданий, помимо someoneElsesJob , может пройти много времени, прежде чем запустится вторая половина myJob . Вероятно, это не входило в намерения разработчика, когда он добавлял setTimeout в myJob .

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

async function myJob() {
  // Run part 1.
  myJobPart1();
  // Yield with scheduler.yield() to break up long task, then run part2.
  await scheduler.yield();
  myJobPart2();
}

Теперь выполнение выглядит так:

Две задачи показаны на панели производительности Chrome DevTools. Обе задачи обозначены как длинные: функция «myJob» берет на себя все выполнение первой задачи, а функция «someoneElsesJob» берет на себя всю работу второй задачи.

У браузера по-прежнему есть возможность реагировать, но теперь продолжение задачи myJob имеет приоритет над запуском новой задачи someoneElsesJob , поэтому myJob завершается до того, как начинается someoneElsesJob . Это намного ближе к предположению о передаче основного потока для поддержания отзывчивости, а не о полном отказе от основного потока.

Приоритетное наследование

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

Однако если установлен приоритет, например, используется низкий 'background' приоритет:

async function lowPriorityJob() {
  part1();
  await scheduler.yield();
  part2();
}

scheduler.postTask(lowPriorityJob, {priority: 'background'});

Продолжение будет запланировано с приоритетом, который выше, чем у других 'background' задач — получение ожидаемого приоритетного продолжения перед любой ожидающей 'background' работой — но все же с более низким приоритетом, чем другие задачи по умолчанию или задачи с высоким приоритетом; это остается 'background' работой.

Это означает, что если вы планируете работу с низким приоритетом с помощью 'background' scheduler.postTask() (или с помощью requestIdleCallback ), продолжение после scheduler.yield() внутри также будет ждать, пока большинство других задач не будут завершены и основной поток не будет простаивать для запуска, а это именно то, что вы хотите получить в задании с низким приоритетом.

Как использовать API

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

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

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

Типы wicg-task-scheduling также можно использовать для проверки типов и поддержки IDE, если вы самостоятельно обнаруживаете функцию scheduler.yield() и добавляете резервный вариант.

Узнать больше

Для получения дополнительной информации об API и о том, как он взаимодействует с приоритетами задач и scheduler.postTask() , ознакомьтесь с документами scheduler.yield() и Prioritized Task Scheduling на MDN.

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

,

Опубликовано: 6 марта 2025 г.

Browser Support

  • Хром: 129.
  • Край: 129.
  • Firefox: не поддерживается.
  • Сафари: не поддерживается.

Source

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

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

scheduler.yield предлагает эргономичный API, который делает именно то, что говорит: выполнение вызванной функции приостанавливается в выражении await scheduler.yield() и переходит к основному потоку, разбивая задачу. Выполнение оставшейся части функции, называемой продолжением функции, будет запланировано в новой задаче цикла событий.

async function respondToUserClick() {
  giveImmediateFeedback();
  await scheduler.yield(); // Yield to the main thread.
  slowerComputation();
}

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

Такие функции, как setTimeout или scheduler.postTask , также можно использовать для разделения задач, но эти продолжения обычно запускаются после любых новых задач, уже поставленных в очередь, потенциально создавая риск длительных задержек между переходом к основному потоку и завершением своей работы.

Приоритетное продолжение после уступки

scheduler.yield является частью API планирования приоритетных задач . Как веб-разработчики, мы обычно не говорим о порядке, в котором цикл событий выполняет задачи с точки зрения явных приоритетов, но относительные приоритеты всегда присутствуют , например, обратный вызов requestIdleCallback , выполняемый после любых поставленных в очередь обратных вызовов setTimeout , или триггерный прослушиватель событий ввода, обычно запускаемый перед тем, как задача поставлена ​​в очередь с помощью setTimeout(callback, 0) .

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

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

Вот пример: две функции, поставленные в очередь для выполнения разных задач с использованием setTimeout .

setTimeout(myJob);
setTimeout(someoneElsesJob);

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

Вот как эта работа может выглядеть в DevTools:

Две задачи показаны на панели производительности Chrome DevTools. Обе задачи обозначены как длинные: функция «myJob» берет на себя все выполнение первой задачи, а функция «someoneElsesJob» берет на себя всю работу второй задачи.

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

function myJob() {
  // Run part 1.
  myJobPart1();
  // Yield with setTimeout() to break up long task, then run part2.
  setTimeout(myJobPart2, 0);
}

Поскольку запуск myJobPart2 был запланирован с помощью setTimeout внутри myJob , но это планирование запускается после того, как someoneElsesJob уже запланирован, вот как будет выглядеть выполнение:

Три задачи, показанные на панели производительности Chrome DevTools. Первая задача запускает функцию «myJobPart1», вторая — длинная задача, выполняющая «someoneElsesJob», и, наконец, третья задача запускает «myJobPart2».

Мы разбили задачу с помощью setTimeout , чтобы браузер мог реагировать в середине myJob , но теперь вторая часть myJob запускается только после того, как someoneElsesJob завершит работу.

В некоторых случаях это может быть хорошо, но обычно это не оптимально. myJob уступал основной поток, чтобы страница могла реагировать на ввод пользователя, а не полностью отказываться от основного потока. В тех случаях, когда someoneElsesJob работает особенно медленно или запланировано много других заданий, помимо someoneElsesJob , может пройти много времени, прежде чем запустится вторая половина myJob . Вероятно, это не входило в намерения разработчика, когда он добавлял setTimeout в myJob .

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

async function myJob() {
  // Run part 1.
  myJobPart1();
  // Yield with scheduler.yield() to break up long task, then run part2.
  await scheduler.yield();
  myJobPart2();
}

Теперь выполнение выглядит так:

Две задачи показаны на панели производительности Chrome DevTools. Обе задачи обозначены как длинные: функция «myJob» берет на себя все выполнение первой задачи, а функция «someoneElsesJob» берет на себя всю работу второй задачи.

У браузера по-прежнему есть возможность реагировать, но теперь продолжение задачи myJob имеет приоритет над запуском новой задачи someoneElsesJob , поэтому myJob завершается до того, как начинается someoneElsesJob . Это намного ближе к предположению о передаче основного потока для поддержания отзывчивости, а не о полном отказе от основного потока.

Приоритетное наследование

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

Однако если установлен приоритет, например, используется низкий 'background' приоритет:

async function lowPriorityJob() {
  part1();
  await scheduler.yield();
  part2();
}

scheduler.postTask(lowPriorityJob, {priority: 'background'});

Продолжение будет запланировано с приоритетом, который выше, чем у других 'background' задач — получение ожидаемого приоритетного продолжения перед любой ожидающей 'background' работой — но все же с более низким приоритетом, чем другие задачи по умолчанию или задачи с высоким приоритетом; это остается 'background' работой.

Это означает, что если вы планируете работу с низким приоритетом с помощью 'background' scheduler.postTask() (или с помощью requestIdleCallback ), продолжение после scheduler.yield() внутри также будет ждать, пока большинство других задач не будут завершены и основной поток не будет простаивать для запуска, а это именно то, что вы хотите получить в задании с низким приоритетом.

Как использовать API

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

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

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

Типы wicg-task-scheduling также можно использовать для проверки типов и поддержки IDE, если вы самостоятельно обнаруживаете функцию scheduler.yield() и добавляете резервный вариант.

Узнать больше

Для получения дополнительной информации об API и о том, как он взаимодействует с приоритетами задач и scheduler.postTask() , ознакомьтесь с документами scheduler.yield() и Prioritized Task Scheduling на MDN.

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

,

Опубликовано: 6 марта 2025 г.

Browser Support

  • Хром: 129.
  • Край: 129.
  • Firefox: не поддерживается.
  • Сафари: не поддерживается.

Source

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

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

scheduler.yield предлагает эргономичный API, который делает именно то, что говорит: выполнение вызванной функции приостанавливается в выражении await scheduler.yield() и переходит к основному потоку, разбивая задачу. Выполнение оставшейся части функции, называемой продолжением функции, будет запланировано в новой задаче цикла событий.

async function respondToUserClick() {
  giveImmediateFeedback();
  await scheduler.yield(); // Yield to the main thread.
  slowerComputation();
}

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

Такие функции, как setTimeout или scheduler.postTask , также можно использовать для разделения задач, но эти продолжения обычно запускаются после любых новых задач, уже поставленных в очередь, потенциально создавая риск длительных задержек между переходом к основному потоку и завершением своей работы.

Приоритетное продолжение после уступки

scheduler.yield является частью API планирования приоритетных задач . Как веб-разработчики, мы обычно не говорим о порядке, в котором цикл событий выполняет задачи с точки зрения явных приоритетов, но относительные приоритеты всегда присутствуют , например, обратный вызов requestIdleCallback , выполняемый после любых поставленных в очередь обратных вызовов setTimeout , или триггерный прослушиватель событий ввода, обычно запускаемый перед тем, как задача поставлена ​​в очередь с помощью setTimeout(callback, 0) .

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

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

Вот пример: две функции, поставленные в очередь для выполнения разных задач с использованием setTimeout .

setTimeout(myJob);
setTimeout(someoneElsesJob);

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

Вот как эта работа может выглядеть в DevTools:

Две задачи показаны на панели производительности Chrome DevTools. Обе задачи обозначены как длинные: функция «myJob» берет на себя все выполнение первой задачи, а функция «someoneElsesJob» берет на себя всю работу второй задачи.

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

function myJob() {
  // Run part 1.
  myJobPart1();
  // Yield with setTimeout() to break up long task, then run part2.
  setTimeout(myJobPart2, 0);
}

Поскольку запуск myJobPart2 был запланирован с помощью setTimeout внутри myJob , но это планирование запускается после того, как someoneElsesJob уже запланирован, вот как будет выглядеть выполнение:

Три задачи, показанные на панели производительности Chrome DevTools. Первая задача запускает функцию «myJobPart1», вторая — длинная задача, выполняющая «someoneElsesJob», и, наконец, третья задача запускает «myJobPart2».

Мы разбили задачу с помощью setTimeout , чтобы браузер мог реагировать в середине myJob , но теперь вторая часть myJob запускается только после того, как someoneElsesJob завершит работу.

В некоторых случаях это может быть хорошо, но обычно это не оптимально. myJob уступал основной поток, чтобы страница могла реагировать на ввод пользователя, а не полностью отказываться от основного потока. В тех случаях, когда someoneElsesJob работает особенно медленно или запланировано множество других заданий, помимо someoneElsesJob , может пройти много времени, прежде чем запустится вторая половина myJob . Вероятно, это не входило в намерения разработчика, когда он добавлял setTimeout в myJob .

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

async function myJob() {
  // Run part 1.
  myJobPart1();
  // Yield with scheduler.yield() to break up long task, then run part2.
  await scheduler.yield();
  myJobPart2();
}

Теперь выполнение выглядит так:

Две задачи показаны на панели производительности Chrome DevTools. Обе задачи обозначены как длинные: функция «myJob» берет на себя все выполнение первой задачи, а функция «someoneElsesJob» берет на себя всю работу второй задачи.

У браузера по-прежнему есть возможность реагировать, но теперь продолжение задачи myJob имеет приоритет над запуском новой задачи someoneElsesJob , поэтому myJob завершается до того, как начинается someoneElsesJob . Это намного ближе к предположению о передаче основного потока для поддержания отзывчивости, а не о полном отказе от основного потока.

Приоритетное наследование

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

Однако если установлен приоритет, например, используется низкий 'background' приоритет:

async function lowPriorityJob() {
  part1();
  await scheduler.yield();
  part2();
}

scheduler.postTask(lowPriorityJob, {priority: 'background'});

Продолжение будет запланировано с приоритетом, который выше, чем у других 'background' задач — получение ожидаемого приоритетного продолжения перед любой ожидающей 'background' работой — но все же с более низким приоритетом, чем другие задачи по умолчанию или задачи с высоким приоритетом; это остается 'background' работой.

Это означает, что если вы планируете работу с низким приоритетом с помощью 'background' scheduler.postTask() (или с помощью requestIdleCallback ), продолжение после scheduler.yield() внутри также будет ждать, пока большинство других задач не будут завершены и основной поток не будет простаивать для запуска, а это именно то, что вы хотите получить в задании с низким приоритетом.

Как использовать API

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

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

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

Типы wicg-task-scheduling также можно использовать для проверки типов и поддержки IDE, если вы самостоятельно определяете функцию scheduler.yield() и добавляете резервный вариант.

Узнать больше

Для получения дополнительной информации об API и о том, как он взаимодействует с приоритетами задач и scheduler.postTask() , ознакомьтесь с документами scheduler.yield() и Prioritized Task Scheduling на MDN.

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

,

Опубликовано: 6 марта 2025 г.

Browser Support

  • Хром: 129.
  • Край: 129.
  • Firefox: не поддерживается.
  • Сафари: не поддерживается.

Source

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

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

scheduler.yield предлагает эргономичный API, который делает именно то, что говорит: выполнение вызванной функции приостанавливается в выражении await scheduler.yield() и переходит к основному потоку, разбивая задачу. Выполнение оставшейся части функции, называемой продолжением функции, будет запланировано в новой задаче цикла событий.

async function respondToUserClick() {
  giveImmediateFeedback();
  await scheduler.yield(); // Yield to the main thread.
  slowerComputation();
}

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

Такие функции, как setTimeout или scheduler.postTask , также можно использовать для разделения задач, но эти продолжения обычно запускаются после любых новых задач, уже поставленных в очередь, потенциально создавая риск длительных задержек между переходом к основному потоку и завершением своей работы.

Приоритетное продолжение после уступки

scheduler.yield является частью API планирования приоритетных задач . Как веб-разработчики, мы обычно не говорим о порядке, в котором цикл событий выполняет задачи с точки зрения явных приоритетов, но относительные приоритеты всегда присутствуют , например, обратный вызов requestIdleCallback , выполняемый после любых поставленных в очередь обратных вызовов setTimeout , или триггерный прослушиватель входных событий, обычно запускаемый перед тем, как задача поставлена ​​в очередь с помощью setTimeout(callback, 0) .

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

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

Вот пример: две функции, поставленные в очередь для выполнения разных задач с использованием setTimeout .

setTimeout(myJob);
setTimeout(someoneElsesJob);

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

Вот как эта работа может выглядеть в DevTools:

Две задачи показаны на панели производительности Chrome DevTools. Обе задачи обозначены как длинные: функция «myJob» берет на себя все выполнение первой задачи, а функция «someoneElsesJob» берет на себя всю работу второй задачи.

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

function myJob() {
  // Run part 1.
  myJobPart1();
  // Yield with setTimeout() to break up long task, then run part2.
  setTimeout(myJobPart2, 0);
}

Поскольку запуск myJobPart2 был запланирован с помощью setTimeout внутри myJob , но это планирование запускается после того, как someoneElsesJob уже запланирован, вот как будет выглядеть выполнение:

Три задачи, показанные на панели производительности Chrome DevTools. Первая задача запускает функцию «myJobPart1», вторая — длинная задача, выполняющая «someoneElsesJob», и, наконец, третья задача — «myJobPart2».

Мы разбили задачу с помощью setTimeout , чтобы браузер мог реагировать в середине myJob , но теперь вторая часть myJob запускается только после того, как someoneElsesJob завершит работу.

В некоторых случаях это может быть хорошо, но обычно это не оптимально. myJob уступал основной поток, чтобы страница могла реагировать на ввод пользователя, а не полностью отказываться от основного потока. В тех случаях, когда someoneElsesJob работает особенно медленно или запланировано множество других заданий, помимо someoneElsesJob , может пройти много времени, прежде чем запустится вторая половина myJob . Вероятно, это не входило в намерения разработчика, когда он добавлял setTimeout в myJob .

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

async function myJob() {
  // Run part 1.
  myJobPart1();
  // Yield with scheduler.yield() to break up long task, then run part2.
  await scheduler.yield();
  myJobPart2();
}

Теперь выполнение выглядит так:

Две задачи показаны на панели производительности Chrome DevTools. Обе задачи обозначены как длинные: функция «myJob» берет на себя все выполнение первой задачи, а функция «someoneElsesJob» берет на себя всю работу второй задачи.

У браузера по-прежнему есть возможность реагировать, но теперь продолжение задачи myJob имеет приоритет над запуском новой задачи someoneElsesJob , поэтому myJob завершается до того, как начинается someoneElsesJob . Это намного ближе к предположению о передаче основного потока для поддержания отзывчивости, а не о полном отказе от основного потока.

Приоритетное наследование

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

Однако если установлен приоритет, например, используется низкий 'background' приоритет:

async function lowPriorityJob() {
  part1();
  await scheduler.yield();
  part2();
}

scheduler.postTask(lowPriorityJob, {priority: 'background'});

Продолжение будет запланировано с приоритетом, который выше, чем у других 'background' задач — получение ожидаемого приоритетного продолжения перед любой ожидающей 'background' работой — но все же с более низким приоритетом, чем другие задачи по умолчанию или задачи с высоким приоритетом; это остается 'background' работой.

Это означает, что если вы планируете работу с низким приоритетом с помощью 'background' scheduler.postTask() (или с помощью requestIdleCallback ), продолжение после scheduler.yield() внутри также будет ждать, пока большинство других задач не будут завершены и основной поток не будет простаивать для запуска, а это именно то, что вы хотите получить в задании с низким приоритетом.

Как использовать API

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

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

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

Типы wicg-task-scheduling также можно использовать для проверки типов и поддержки IDE, если вы самостоятельно определяете функцию scheduler.yield() и добавляете резервный вариант.

Узнать больше

Для получения дополнительной информации об API и о том, как он взаимодействует с приоритетами задач и scheduler.postTask() , ознакомьтесь с документами scheduler.yield() и Prioritized Task Scheduling на MDN.

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