Лучшее планирование JS с помощью isInputPending(), Лучшее планирование JS с помощью isInputPending()

Новый API JavaScript, который поможет вам избежать компромисса между производительностью загрузки и скоростью отклика на ввод.

Нейт Шлосс
Nate Schloss
Эндрю Комминос
Andrew Comminos

Быстрая загрузка затруднена. Сайты, использующие JS для отображения своего контента, в настоящее время вынуждены искать компромисс между производительностью загрузки и скоростью реагирования на ввод: либо выполнять всю работу, необходимую для отображения, одновременно (лучшая производительность загрузки, худшая скорость отклика на ввод), либо разбивать работу на более мелкие части. задач, чтобы оставаться отзывчивым на ввод и рисование (худшая производительность при загрузке, лучшая реакция на ввод).

Чтобы устранить необходимость идти на этот компромисс, Facebook предложил и реализовал API isInputPending() в Chromium, чтобы улучшить скорость реагирования без уступок. Основываясь на отзывах о пробной версии Origin, мы внесли ряд обновлений в API и рады сообщить, что API теперь поставляется по умолчанию в Chromium 87!

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

Поддержка браузера

  • 87
  • 87
  • Икс
  • Икс

isInputPending() поставляется в браузерах на базе Chromium, начиная с версии 87. Ни один другой браузер не сообщил о намерении выпустить этот API.

Фон

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

На данный момент лучшая практика — решать эту проблему, разбивая JavaScript на более мелкие блоки. Пока страница загружается, она может запустить немного JavaScript, а затем передать управление обратно браузеру. Затем браузер может проверить свою очередь входных событий и посмотреть, есть ли что-нибудь, о чем ему нужно сообщить странице. Затем браузер может вернуться к запуску блоков JavaScript по мере их добавления. Это помогает, но может вызвать другие проблемы.

Каждый раз, когда страница возвращает управление браузеру, браузеру требуется некоторое время, чтобы проверить свою очередь входных событий, обработать события и выбрать следующий блок JavaScript. Хотя браузер быстрее реагирует на события, общее время загрузки страницы замедляется. А если мы поддаемся слишком часто, страница загружается слишком медленно. Если мы будем уступать реже, браузеру потребуется больше времени, чтобы реагировать на пользовательские события, и люди будут разочарованы. Не весело.

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

В Facebook мы хотели посмотреть, как будет выглядеть ситуация, если мы придумаем новый подход к загрузке, который устранит этот разочаровывающий компромисс. Мы обратились по этому поводу к нашим друзьям из Chrome и предложили использовать isInputPending() . API isInputPending() является первым, использующим концепцию прерываний для ввода данных пользователем в Интернете и позволяет JavaScript проверять ввод, не уступая браузеру.

Диаграмма, показывающая, что isInputPending() позволяет вашему JS проверять, есть ли ожидающий пользовательский ввод, без полной передачи выполнения браузеру.

Поскольку к API был проявлен интерес, мы объединились с нашими коллегами из Chrome, чтобы реализовать и внедрить эту функцию в Chromium. С помощью инженеров Chrome мы получили исправления в рамках исходной пробной версии (которая позволяет Chrome протестировать изменения и получить отзывы от разработчиков перед полным выпуском API).

Мы учли отзывы участников исходной пробной версии и других членов рабочей группы W3C по веб-производительности и внесли изменения в API.

Пример: более производительный планировщик

Предположим, что вам нужно выполнить кучу работы по блокировке отображения для загрузки страницы, например, создать разметку из компонентов, разложить простые числа или просто нарисовать крутой индикатор загрузки. Каждый из них разбит на отдельный рабочий элемент. Используя шаблон планировщика, давайте набросаем, как мы могли бы обрабатывать нашу работу с помощью гипотетической processWorkQueue() :

const DEADLINE = performance.now() + QUANTUM;
while (workQueue.length > 0) {
  if (performance.now() >= DEADLINE) {
    // Yield the event loop if we're out of time.
    setTimeout(processWorkQueue);
    return;
  }
  let job = workQueue.shift();
  job.execute();
}

Вызывая processWorkQueue() позднее в новой макрозадаче через setTimeout() , мы даем браузеру возможность оставаться в некоторой степени отзывчивым на ввод (он может запускать обработчики событий до возобновления работы), сохраняя при этом возможность работать относительно непрерывно. Тем не менее, мы можем быть отложены на долгое время из-за другой работы, требующей контроля над циклом событий, или получить дополнительные QUANTUM миллисекунды задержки событий.

Это нормально, но можем ли мы сделать лучше? Абсолютно!

const DEADLINE = performance.now() + QUANTUM;
while (workQueue.length > 0) {
  if (navigator.scheduling.isInputPending() || performance.now() >= DEADLINE) {
    // Yield if we have to handle an input event, or we're out of time.
    setTimeout(processWorkQueue);
    return;
  }
  let job = workQueue.shift();
  job.execute();
}

Введя вызов navigator.scheduling.isInputPending() , мы можем быстрее реагировать на ввод, в то же время гарантируя, что в противном случае наша работа по блокировке отображения будет выполняться непрерывно. Если мы не заинтересованы в обработке чего-либо, кроме ввода (например, рисования), пока работа не будет завершена, мы также можем легко увеличить длину QUANTUM .

По умолчанию «непрерывные» события не возвращаются из isInputPending() . К ним относятся mousemove , pointermove и другие. Если вы тоже заинтересованы в получении прибыли за это, нет проблем. Предоставляя объект isInputPending() с includeContinuous установленным в true , мы готовы к работе:

const DEADLINE = performance.now() + QUANTUM;
const options = { includeContinuous: true };
while (workQueue.length > 0) {
  if (navigator.scheduling.isInputPending(options) || performance.now() >= DEADLINE) {
    // Yield if we have to handle an input event (any of them!), or we're out of time.
    setTimeout(processWorkQueue);
    return;
  }
  let job = workQueue.shift();
  job.execute();
}

Вот и все! Такие фреймворки, как React, встраивают поддержку isInputPending() в свои основные библиотеки планирования, используя аналогичную логику. Будем надеяться, что это позволит разработчикам, использующим эти платформы, получить преимущества от isInputPending() без значительных переписываний.

Уступать не всегда плохо

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

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

Помните и о других страницах, которые используют общий цикл событий. На таких платформах, как Chrome для Android, несколько источников часто используют один и тот же цикл событий. isInputPending() никогда не вернет true , если входные данные отправляются в кадр с перекрестным происхождением, и, таким образом, фоновые страницы могут мешать отзывчивости страниц переднего плана. Возможно, вам захочется чаще сокращать, откладывать или прекращать работу при выполнении работы в фоновом режиме с помощью API видимости страниц .

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

Обратная связь

  • Оставьте отзыв о спецификации в репозитории , ожидающем ввода .
  • Свяжитесь с @acomminos (одним из авторов спецификации) в Твиттере.

Заключение

Мы очень рады запуску isInputPending() и тому, что разработчики могут начать использовать его уже сегодня. Этот API — первый случай, когда Facebook создал новый веб-API и прошел путь от инкубации идеи до предложения стандартов и фактической реализации в браузере. Мы хотели бы поблагодарить всех, кто помог нам дойти до этого момента, и выразить особую благодарность всем в Chrome, кто помог нам воплотить эту идею в жизнь и реализовать ее!

Героическое фото Уилла Х. МакМахана на Unsplash .