Итак, Представим что перед вами задача: реализовать кнопку «Редактировать профиль», по нажатию на которую должна появиться форма. А значения для полей этой формы загружаются асинхронно с сервера.
Не отзывчивый интерфейс
Пример реализации такой кнопки.
Что здесь происходит и почему этот вариант плох
Как вы могли заметить, реакция на клик по кнопке происходит с большой задержкой. Почему так? Давайте посмотрим на график:
Как видите, в данном подходе скрипт сначала загружает данные и выполняет подготовительную работу, а уже потом изменяет интерфейс. Это ошибка.
Интерфейс должен как-либо реагировать на действие пользователя так рано как это возможно.
Отзывчивый интерфейс
Давайте немного улучшим ситуацию. В следующем примере мы создаём форму и сразу её отображаем. А уже потом навешиваем обработчики и загружаем данные с сервера.
Посмотрим на наш график:
Как видно общее время выполнения не изменилось. Однако теперь пользователь видит реакцию на свои действия намного раньше.
Отложенный Promise
Вы знали что вам не обязательно дожидаться Promise в том же месте где вы его создали?
const promise = fetch()
// Любой код здесь будет выполнен сразу, синхронно, не ожидая завершения fetch()
const response = await promise // ожидаем завершения promise
Так вы можете минимизировать время простоя — когда браузер просто ожидает завершения асинхронной операции и ничем не занят.
Запускайте длинные, асинхронные операции как можно раньше, но ожидайте их завершения как можно позже.
Теперь, держа в голове это правило давайте вернемся к нашей форме.
В следующем примере мы будем отправлять запрос на загрузку данных сразу после клика, но не будем дожидаться его завершения. Вместо простоя во время ожидания ответа от сервера, мы выполним полезную работу — создадим форму и отобразим её.
И вот что мы получаем:
Как видите на графике, в самом начале два процесса выполняются параллельно. Таким образом мы сэкономили почти секунду времени выполнения.
Надеюсь данный материал будет кому-то полезен
Комментарии (18)
polyakov_andrey
21.10.2019 19:42Вариант отображения прелоадера, либо скелетона формы не лучше будет? Пользователь получает форму, которая пуста, начинает заполнять, при этом в какой-то момент приходит ответ от сервера, который перезаписывает ранее полученные значения. По мне, это как-то не очень хорошо
Kozack Автор
21.10.2019 20:00Поэтому до того как загрузятся данные форма заблокирована. Суть в том, чтобы выполнить максимальное количество работы пока загружаются данные. Чтобы когда они загрузились — осталось выполнить минимум. Если отображать прелоадер, то потом его нужно удалять и заменять формой. Лучше всего показать форму, с прелоадером внутри. Но это уже детали выходящие за пределы темы материала.
Griboks
21.10.2019 20:21+1Пример слишком плохой, чтобы быть хорошим. Классическое решение — отвлечь пользователя. Он не должен следить за тем, в каких полях есть текст, а в каких ещё не загрузился.
1. Нажимает кнопку.
2. Появляется плейсхолдер/лоадер/анимация/что-то яркое и выделяющееся/тип/краткая справка или доп. информация. И не забывает интуитивно понятный интерфейс, а то ещё подумает, что страница зависла и обновит её.
3. Когда все данные готовы, отображается форма. Причём контрастно с заглушкой, чтобы это было видно беглым взглядом.
Но сама идея, что следует использовать промисы. Это да… это одобряю.funca
21.10.2019 23:05Проблема с промисами в том, что их нельзя отметить. Создавая промисы заранее не всегда очевидно, будет-ли еще нужен результат в будущем. В промежутке может произойти ошибка, пользователь может передумать и т.п. Когда задача выглядит как процесс, т.е имеет зависимость от времени, удобнее использовать такие структуры как итераторы, Observable, streams и т.п.
Griboks
21.10.2019 23:17Согласен, но результат промисов можно просто не использовать. Хотя, это зависит от архитектуры.
AriesUa
22.10.2019 10:30Что касается ошибки промиса, то его всегда можно поймать в «catch» и уже обработать. Показать сообщение об ошибке/сделать редирект/итд.
Что касаемо отмены промиса. То здесь работа не для промиса, а для самого реквеста. XMLHttpRequest/fetch позволяют вызвать cancel запроса. Промис, он служит для ожидания выполнения.
morr
21.10.2019 23:51Я стараюсь придерживать правила, что чем проще сделано, тем лучше.
Пустая выключенная форма потребует дополнительных стилей и логики. Если их написания можно избежать, то лучше так и сделать. То есть просто показать крутилку на месте формы.
Подобные оптимизации имеют право на жизнь, но применять их на практике стоит по необходимости, когда что-то начинает тормозить.Kozack Автор
22.10.2019 09:42Простота — важна.
Но, суть в том, чтобы не тратить время и в процессе ожидания асинхронного ответа выполнить максимум работы. Выполнить все операции с DOM, добавить элементы где нужно, проставить классы если нужно, применить стили и т.д. Чтобы когда асинхронная операция закончится у вас уже всё было готово. Это не обязательно предложенная мною «выключенная форма».morr
22.10.2019 15:40Может быть добавить нечто подобное в конец статьи? Статья же заявлена как туториал новичкам.
bvn13
FF 69.0.2 (64-bit) on Ubuntu 18.04.3 — ни одна ваша кнопка не делает ничего. Все примеры неотзывчивы. Ну, либо я что-то делаю не так.
igormich88
FF 69.0.3 (64-битный), Windows 7
Аналогичное поведение, нет реакции на нажатие кнопок.
Denai
71.0b2 (64-битный), win10. Аналогично
Kozack Автор
Всё из-за того, что FF не работает с тегом
. Изменил примеры, но на суть статьи это не влияет.Vitalley
У меня есть, но никакой разницы я не увидел