Обрабатываем вызовы api изящным образом

Оригинал

Обрабатываем вызовы api изящным образом

Оригинал

Фото Sven Fischer из Unsplash

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

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

Тяжесть заготовок кода (boilerplate)

Предположим следующие бизнес требования:

1. Отправить запрос API, чтобы получить список сегодняшних новостей.
2. Показать крутилку во время загрузки
3. Показать загруженный список новостей при успешной загрузке.

Многие разработчики проектируют модель состояния с двумя действиями (скажем, LoadNews и LoadNewsSuccess, и двумя обработчиками редуктора для изменения состояния loading и entities).

Пока что мы не видим здесь никаких проблем. Это крепкий «стандарт».

Допустим, у нас есть 20 (или даже больше) запросов API в этом приложении. Теперь появляются проблемы:

1. Много заготовок кода. Нам нужно обработать состояние API loading 20 раз, сделать 40 действий и 40 обработчиков в редукторах. Это много кода с повторяющейся логикой.

2. Несогласованное именование. Допустим, 20 вызовов API реализованы 4 разработчиками. Они могут иметь разные соглашения об именах. Например, загрузка может называться isLoading, waiting, isWaiting, started и т. д.

На самом деле, приведенная выше модель состояния API имеет только одно состояние loading. Однако предполагается, что полный набор будет иметь больше состояний API (о которых пойдет речь в следующем разделе), что сделает предыдущие 2 пункта еще хуже.

Давайте решим эту проблему изящно.

Что такое полный набор состояний?

Полный цикл вызовов API может иметь следующие состояния:

1. Вызов API не запущен
2. Вызов API начался, но ответа пока нет
3. API-вызов получил успешный ответ
4. Вызов API получил ответ об ошибке

Таким образом, мы можем спроектировать общую модель загрузки следующим образом (назовем ее Loadable):

4 состояния легко сопоставить со значениями 3 полей.

Я бы также создал 4 простых вспомогательных функции для обновления загружаемого состояния. Обратите внимание, что они являются чистыми функциями и возвращают новые объекты loadable:

Применим loadable к нашему примеру загрузки списка новостей

Модель

Помимо 3 полей loadable, нам нужно еще одно состояние для хранения списка новостей, которые мы получили от API. Итак, мы можем предположить следующую модель:

Действия (actions)

Действия остаются такими же, как в соглашениях ngrx.

Редуктор (reducer)

Мы используем редуктор, чтобы изменить состояние в соответствии с 3 различными действиями.

Побочные эффекты (effects)

UI Component

Кода достаточно, чтобы заставить его работать. Тем не менее, это помогает только обеспечить согласованное именование за счет наследования loadable, и помогает убедиться в правильности изменения состояния с помощью вспомогательных функций. Это действительно не уменьшает шаблон. Представьте, что если у нас есть 20 вызовов API, нам все равно нужно обрабатывать каждое действие (load, loadSuccess, loadError) в каждом из 20 редукторов. И 20 из них имеют одинаковую логику смены состояний. (то есть loading success error)

Абстрактная логика изменения состояния API для редуктора

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

Таким образом, редуктор для новостей может быть таким:

baseNewsReducer обрабатывает не loadable состояния (то есть entities)

newsReducer на самом деле будет применять withLoadable к baseReducer, чтобы придать baseReducer немного «магии», т.е. способность автоматически обрабатывать изменения состояния loadable.

Таким образом, если у нас есть 20 вызовов API, и мы хотим сохранить все 20 * 3 = 60 состояний, мы можем просто применить withLoadable к 20 базовым редукторам. В 20 базовых редукторах нас не волнует, как должно обновляться состояние loadable. Таким образом, это экономит нам много времени на ручное обновление состояния API.

Бонус: подключение Loadable в компонент пользовательского интерфейса

На самом деле Loadable обеспечивает действительно согласованный контракт, так что он может быть беспрепятственно связан с глобальным компонентом пользовательского интерфейса. Например, я могу создать общий loadable-container компонента для обработки пользовательского интерфейса загрузки, глобальный интерфейс ошибок. И единственный контракт с внешним миром — это просто Loadable в @Input

Это позволит нам показывать каждую крутилку/ошибку вызова API, просто используя этот компонент loadable-container, что также экономит множество кода в шаблонах.

HTML код компонента

Пожалуйста, посмотрите окончательный код в StackBlitz или в Github Repo. Единственное отличие от кода статьи в том, что он более строго организован, чтобы показать лучшие практики кодирования в реальной жизни. Кроме того, он использует ложный вызов API для получения списка новостей.

Если вы хотите использовать его в своем проекте, у меня есть пакет npm для вас. Смотрите тут.

Примечание переводчика: примеры кода дополнены комментариями, исправлены незначительные ошибки, текст незначительно сокращён для более литературно красивого перевода.