В предыдущей итерации мы разделили компоненты:

Но наша попытка поставить SearchResults компонент в очередь дорогостоящей searchEngine работы в очереди событий JavaScript и запустить его в отдельном стеке выполнения не удалась. Так как еще мы можем попытаться встать в очередь searchEngine.search?

Есть два хорошо известных метода настройки асинхронных обратных вызовов: Promises и setTimeout.

Обещания

Посмотрим, как это выглядит в профилировщике:

Опять сорвано!

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

Если вы внимательно посмотрите на профиль, обратный вызов будет помещен в раздел Run Microtasks, поскольку обратные вызовы Promise считаются микрозадачей. Браузер обычно проверяет и запускает микрозадачи сразу после завершения выполнения обычного стека, что-то вроде последней секунды в конце текущего выполнения.

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

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

setTimeout

Обзор говорит:

Ах ах! Вот и все! Обратите внимание на два стека выполнения: Event (keypress) и Timer Fired(searchResults.js:49). И между двумя стопками (там, где есть синяя линия) есть краска. Это именно то, что мы предполагали! Давайте посмотрим на этот красивый интерфейс в действии!

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

Анализировать этот конкретный профиль и понять, что вызывает задержку, непросто. Несколько полезных замечаний по использованию профиля:

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

Нажатие клавиши: время от нажатия клавиши до запуска Event (keypress) обработчика.
Ключевой символ: время от нажатия клавиши до отображения обновления в браузере. .

В идеале взаимодействие Key Down должно быть очень коротким, потому что, если ничто не блокирует основной поток JavaScript, событие должно срабатывать сразу после нажатия клавиши. Так что везде, где вы видите длинную Key Downs, это уже проблема с блокировкой пользовательского интерфейса.

В этом случае причиной более длинного Key Downs является то, что они запускаются, когда основной поток заблокирован дорогостоящим setTimeoutCallback. Взять хотя бы последний Key Down. К сожалению, он рассчитан прямо в начале setTimeoutCallback, который запускает дорогостоящий search метод. Это означает, что Key Down не может активировать свое событие, пока не будет выполнено search вычисление. Для ощущения масштаба в этом профиле эти search методы занимают около 330 мс. Это ⅓ секунды. Это очень долгое время, чтобы заблокировать основной поток в мире хорошего пользовательского интерфейса.

Что особенно интересно мне, так это последний Key Character. Даже после того, как связанный Key Down завершает работу и запускает Event (keypress), браузер не отображает новый searchTerm и вместо этого запускает другой setTimeoutCallback. Вот почему это Key Character взаимодействие длится вдвое дольше.

Это было и остается для меня большим сюрпризом. Весь смысл перемещения search в setTimeoutCallback заключается в том, чтобы дать браузеру возможность рисовать перед запуском setTimeoutCallback. Но после этого последнего Event (keypress) (там, где есть синяя линия) краски нет.

Тогда мы можем сделать вывод, что мы не можем полагаться на наши теории о том, как браузер выбирает задачи в очереди. По-видимому, он не всегда отдает приоритет рисованию над обратными вызовами тайм-аута. И когда ваши обратные вызовы тайм-аута могут занять 330 мс в основном потоке, это приводит к слишком долгой блокировке потока и неизбежно вызывает задержку.

БОНУС

Дополнительная благодарность любому, кто может объяснить, почему setTimeoutCallback имеет приоритет над краской.

Я помню, как часами смотрел на эти профили, ломая голову над тем, что еще можно сделать для повышения производительности. Я был расстроен и отчетливо помню, как в раздражении сказал вслух: «Черт возьми, JavaScript и его единственный поток…»

и вдруг идея:

v1.2.0 Многопоточность