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

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

Не отзывчивый интерфейс


Пример реализации такой кнопки.


Что здесь происходит и почему этот вариант плох


Как вы могли заметить, реакция на клик по кнопке происходит с большой задержкой. Почему так? Давайте посмотрим на график:



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


Отзывчивый интерфейс


Давайте немного улучшим ситуацию. В следующем примере мы создаём форму и сразу её отображаем. А уже потом навешиваем обработчики и загружаем данные с сервера.



Посмотрим на наш график:



Как видно общее время выполнения не изменилось. Однако теперь пользователь видит реакцию на свои действия намного раньше.

Отложенный Promise


Вы знали что вам не обязательно дожидаться Promise в том же месте где вы его создали?

const promise = fetch()

// Любой код здесь будет выполнен сразу, синхронно, не ожидая завершения fetch()

const response = await promise // ожидаем завершения promise


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

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



И вот что мы получаем:



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

Надеюсь данный материал будет кому-то полезен

Комментарии (18)


  1. bvn13
    21.10.2019 19:22
    +1

    FF 69.0.2 (64-bit) on Ubuntu 18.04.3 — ни одна ваша кнопка не делает ничего. Все примеры неотзывчивы. Ну, либо я что-то делаю не так.


    1. igormich88
      21.10.2019 22:52

      FF 69.0.3 (64-битный), Windows 7
      Аналогичное поведение, нет реакции на нажатие кнопок.


      1. Denai
        22.10.2019 02:45

        71.0b2 (64-битный), win10. Аналогично


    1. Kozack Автор
      22.10.2019 09:54

      Всё из-за того, что FF не работает с тегом

      <dialog>
      . Изменил примеры, но на суть статьи это не влияет.


    1. Vitalley
      22.10.2019 16:49

      У меня есть, но никакой разницы я не увидел


  1. tuxi
    21.10.2019 19:27

    FF 69.0.1 / win7 64 pro
    никакой разницы не почувствовал


  1. polyakov_andrey
    21.10.2019 19:42

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


    1. Kozack Автор
      21.10.2019 20:00

      Поэтому до того как загрузятся данные форма заблокирована. Суть в том, чтобы выполнить максимальное количество работы пока загружаются данные. Чтобы когда они загрузились — осталось выполнить минимум. Если отображать прелоадер, то потом его нужно удалять и заменять формой. Лучше всего показать форму, с прелоадером внутри. Но это уже детали выходящие за пределы темы материала.


  1. Griboks
    21.10.2019 20:21
    +1

    Пример слишком плохой, чтобы быть хорошим. Классическое решение — отвлечь пользователя. Он не должен следить за тем, в каких полях есть текст, а в каких ещё не загрузился.
    1. Нажимает кнопку.
    2. Появляется плейсхолдер/лоадер/анимация/что-то яркое и выделяющееся/тип/краткая справка или доп. информация. И не забывает интуитивно понятный интерфейс, а то ещё подумает, что страница зависла и обновит её.
    3. Когда все данные готовы, отображается форма. Причём контрастно с заглушкой, чтобы это было видно беглым взглядом.

    Но сама идея, что следует использовать промисы. Это да… это одобряю.


    1. funca
      21.10.2019 23:05

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


      1. Griboks
        21.10.2019 23:17

        Согласен, но результат промисов можно просто не использовать. Хотя, это зависит от архитектуры.


      1. AriesUa
        22.10.2019 10:30

        Что касается ошибки промиса, то его всегда можно поймать в «catch» и уже обработать. Показать сообщение об ошибке/сделать редирект/итд.

        Что касаемо отмены промиса. То здесь работа не для промиса, а для самого реквеста. XMLHttpRequest/fetch позволяют вызвать cancel запроса. Промис, он служит для ожидания выполнения.


      1. VolCh
        23.10.2019 07:18

        Замыкания в JS позволяют создавать "отменяемые промисы", "промисы с таймаутами" и т. п.


        1. Kozack Автор
          23.10.2019 12:20

          Попробуйте замерить время выполнения. Скажем с помощью console.time()


        1. funca
          23.10.2019 23:03

          Есть много структур, для которых можно продумать осмысленное преобразование к промису. В конце концов промис это абстракция над значением (value).


  1. morr
    21.10.2019 23:51

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

    Подобные оптимизации имеют право на жизнь, но применять их на практике стоит по необходимости, когда что-то начинает тормозить.


    1. Kozack Автор
      22.10.2019 09:42

      Простота — важна.
      Но, суть в том, чтобы не тратить время и в процессе ожидания асинхронного ответа выполнить максимум работы. Выполнить все операции с DOM, добавить элементы где нужно, проставить классы если нужно, применить стили и т.д. Чтобы когда асинхронная операция закончится у вас уже всё было готово. Это не обязательно предложенная мною «выключенная форма».


      1. morr
        22.10.2019 15:40

        Может быть добавить нечто подобное в конец статьи? Статья же заявлена как туториал новичкам.