Статьи

Как запланировать фоновые задачи в JavaScript

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

Представьте, что волшебный процессор Pixie заставляет ваш браузер работать. Все это обрабатывается этим единственным пикси, будь то рендеринг HTML, реакция на команду меню, рисование на экране, обработка щелчка мыши или запуск функции JavaScript. Как и большинство из нас, пикси может делать только одну вещь за раз. Если мы бросим много задач в пикси, они будут добавлены в большой список дел и обработаны по порядку.

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

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

  • запись аналитических данных
  • отправка данных в социальные сети (или добавление 57 кнопок «поделиться»)
  • предварительная загрузка контента
  • предварительная обработка или предварительный рендеринг HTML

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

Одним из вариантов является использование веб-работников, которые могут запускать код одновременно в отдельном потоке. Это отличный вариант для предварительной выборки и обработки, но вам не разрешен прямой доступ или обновление DOM. Вы можете избежать этого в своих собственных сценариях, но не можете гарантировать, что это никогда не потребуется в сторонних сценариях, таких как Google Analytics.

Другая возможность — это setTimeout , например, setTimeout(doSomething, 1); , Браузер выполнит doSomething() после завершения других немедленно выполняемых задач. По сути, он находится в нижней части списка дел. К сожалению, функция будет вызываться независимо от спроса на обработку.

requestIdleCallback

requestIdleCallback — это новый API, предназначенный для планирования второстепенных фоновых задач в те моменты, когда браузер делает передышку. Это напоминает requestAnimationFrame, который вызывает функцию для обновления анимации перед следующей перерисовкой. Вы можете прочитать больше о requestAnimationFrame здесь: Простые анимации с использованием requestAnimationFrame

Мы можем определить, поддерживается ли requestIdleCallback следующим образом:

 if ('requestIdleCallback' in window) { // requestIdleCallback supported requestIdleCallback(backgroundTask); } else { // no support - do something else setTimeout(backgroundTask1, 1); setTimeout(backgroundTask2, 1); setTimeout(backgroundTask3, 1); } 

Вы также можете указать параметр объекта параметров с тайм-аутом (в миллисекундах), например

 requestIdleCallback(backgroundTask, { timeout: 3000; }); 

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

requestIdleCallback вызывает вашу функцию только один раз и передает объект deadline со следующими свойствами:

  • didTimeout — установить true, если didTimeout необязательный тайм-аут
  • timeRemaining() — функция, которая возвращает количество миллисекунд, оставшихся для выполнения задачи

timeRemaining() выделит не более 50 timeRemaining() для запуска вашей задачи. Это не остановит задачи, превышающие этот лимит, но, предпочтительно, вам следует снова вызвать requestIdleCallback чтобы запланировать дальнейшую обработку.

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

 // array of functions to run var task = [ background1, background2, background3 ]; if ('requestIdleCallback' in window) { // requestIdleCallback supported requestIdleCallback(backgroundTask); } else { // no support - run all tasks soon while (task.length) { setTimeout(task.shift(), 1); } } // requestIdleCallback callback function function backgroundTask(deadline) { // run next task if possible while (deadline.timeRemaining() > 0 && task.length > 0) { task.shift()(); } // schedule further tasks if necessary if (task.length > 0) { requestIdleCallback(backgroundTask); } } 

Что-нибудь, что не должно быть сделано в requestIdleCallback?

Как отмечает Пол Льюис в своем блоге на эту тему , работа, которую вы выполняете в requestIdleCallback, должна выполняться небольшими порциями. Он не подходит для чего-либо с непредсказуемым временем выполнения (например, манипулирование DOM, что лучше сделать с помощью обратного вызова requestAnimationFrame). Вам также следует остерегаться разрешать (или отклонять) обещания, поскольку обратные вызовы будут выполняться сразу после завершения обратного вызова в режиме ожидания, даже если времени больше не осталось.

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

requestIdleCallback — экспериментальная функция, и спецификация все еще находится в процессе разработки, поэтому не удивляйтесь, когда вы столкнетесь с изменениями API. Он поддерживается в Chrome 47 … который должен быть доступен до конца 2015 года. Opera также должна получить эту функцию в ближайшее время. Microsoft и Mozilla рассматривают API, и это звучит многообещающе. Там нет ни слова от Apple, как обычно. Если вам захочется сделать это сегодня, то лучше всего использовать Chrome Canary (гораздо более новый выпуск Chrome, который не так хорошо протестирован, но имеет последние блестящие вещи).

Пол Льюис (упомянутый выше) создал простую прокладку requestIdleCallback . Это реализует API, как описано, но это не полизаполнение, которое может эмулировать поведение браузера при обнаружении простоя. Он прибегает к использованию setTimeout как в примере выше, но это хороший вариант, если вы хотите использовать API без обнаружения объектов и разветвления кода.

Хотя поддержка ограничена сегодня, requestIdleCallback может быть интересным средством, чтобы помочь вам максимизировать производительность веб-страницы. Но что вы думаете? Буду рад услышать ваши мысли в разделе комментариев ниже.