Вяз. Удобно И Неловко. Http, Задача

Давайте продолжим говорить о Вяз 0.18 .

Вяз.

Удобно и неудобно Вяз.

Удобно и неловко.

Состав Вяз.

Удобно и неловко.

Json.Encoder и Json.Decoder В этой статье мы рассмотрим вопросы взаимодействия с серверной частью.



Выполнение запросов

Примеры простых запросов можно найти в описании пакета.

HTTP .

Тип запроса - Http.Запросить .

Тип результата запроса — Результат HTTP.Ошибка а.

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

Выполнить запрос можно с помощью функций:

  1. HTTP.отправить;
  2. http.toTask.
Http.send позволяет выполнить запрос и по завершении передает сообщение функции обновления, указанной в первом аргументе.

Сообщение несет данные о результате запроса.

Http.toTask позволяет вам создать Задача , что можно сделать.

Использование функции Http.toTask, на мой взгляд, наиболее удобно, поскольку экземпляры Task можно объединять друг с другом при используя различные функции , Например Задача.

map2 .

Давайте посмотрим на пример.

Допустим, для сохранения пользовательских данных необходимо выполнить два последовательных зависимых запроса.

Пусть это будет создание поста от пользователя и сохранение к нему фотографий (используется определенный CDN).

Сначала давайте посмотрим на реализацию случая Http.Send. Для этого нам понадобятся две функции:

  
  
  
  
  
  
  
  
   

save : UserData -> Request Http.Error UserData save userData = Http.post “/some/url” (Http.jsonBody (encodeUserData userData)) decodeUserData saveImages : Int -> Images -> Request Http.Error CDNData saveImages id images = Http.post (“/some/cdn/for/” ++ (toString id)) (imagesBody images) decodedCDNData

Типы UserData и CDNData описываться не будут; они не важны для примера.

Функция encodeUserData является кодировщиком.

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

Функция imagesBody генерирует тело запроса типа multipart/форма-данные .

Функции decodeUserData и decodedCDNData декодируют ответ сервера для пользовательских данных и результат запроса CDN соответственно.

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

type Msg = DataSaved (Result Http.Error UserData) | ImagesSaved (Result Http.Error CDNData)

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

Например, это может выглядеть так:

update : Msg -> Model -> (Model, Cmd Msg) update msg model case Msg of ClickedSomething -> (model, Http.send DataSaved (save model.userData))

В этом случае создается запрос и отмечается сообщением DataSaved. Затем получено это сообщение:

update : Msg -> Model -> (Model, Cmd Msg) update msg model case Msg of DataSaved (Ok userData) -> ( {model | userData = userData}, Http.send ImagesSaved (saveImages userData.id model.images)) DataSaved (Err reason) -> (model, Cmd.None)

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

Обработка сообщения ImagesSaved будет аналогична обработке DataSaved, и потребуется обрабатывать случаи успеха и неудачи.

Теперь рассмотрим вариант использования функции Http.toTask. Используя описанные функции, определяем новую функцию:

saveAll : UserData -> Images -> Task Http.Error (UserData, CDNData) saveAll : userData images = save model.userData |> Http.toTask |> Task.andThen (\newUserData -> saveImages usersData.id images |> Http.toTask |> Task.map (\newImages -> (userData, newImages) } )

Теперь, используя возможность объединения задач, мы можем получать все данные в виде одного сообщения:

type Msg = Saved (Result Http.Error (UserData, CDNData)) update : Msg -> Model -> (Model, Cmd Msg) update msg model case Msg of ClickedSomething -> (model, Task.attempt Saved (saveAll model.userData model.images)) DataSaved (Ok (userData, images)) -> ( {model | userData = userData, images = images}, Cmd.none) DataSaved (Err reason) -> (model, Cmd.None)

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

попытка , что позволяет выполнить задачу.

Не путать с функцией Задача.

выполнить .

Task.perform — позволяет выполнять задачи, которые не могу потерпеть неудачу .

Task.attempt — выполняет задачи, которые может потерпеть неудачу .

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

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

-> Task Http.Error a.

Статус выполнения запроса

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

В целом состояние запроса можно описать так:

  1. запрос не был выполнен;
  2. запрос выполнен;
  3. запрос выполнен успешно;
  4. запрос не удался.

Есть пакет под такое описание Удаленные данные .

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

Вместо этого пакета появились следующие правила:

  1. Объявите все данные с сервера как тип Maybe. В данном случае «Ничто» означает отсутствие данных;
  2. объявите атрибут загрузки типа Int в модели приложения или компонента.

    Параметр хранит количество выполняемых запросов.

    Единственное неудобство такого подхода — необходимость увеличивать и уменьшать атрибут в начале запроса и в конце соответственно;

  3. объявите атрибут ошибок типа List String в модели приложения или компонента.

    Этот атрибут используется для хранения данных об ошибках.

Описанная схема, как показывает практика, ненамного лучше варианта с пакетом RemoteData. Если у кого-то есть другие варианты, поделитесь в комментариях.

Статус выполнения запроса должен включать ход загрузки из пакета.

Http.Прогресс .



Последовательность задач

Давайте рассмотрим варианты последовательностей задач, которые часто встречаются при разработке:
  1. последовательные зависимые задачи;
  2. последовательные самостоятельные задачи;
  3. параллельные независимые задачи.

Последовательные зависимые задачи уже обсуждались выше; в этом разделе я дам общее описание и подходы к реализации.

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

В случае успеха возвращается некоторая комбинация результатов:

someTaskA |> Task.andThen (\resultA -> someTaskB |> Task.map (\resultB -> (resultA, resultB) ) )

Этот код создает задачу типа Ошибка задачи (a, b), которую можно выполнить позже.

Функция Task.andThen позволяет отправить на выполнение новую задачу, если предыдущая выполнена успешно.

Функция Task.map позволяет конвертировать результаты выполнения в случае успеха.

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

Предположим, что идентификаторы пользователей одинаковы:

someTaskA |> Task.andThen (\resultA -> someTaskB |> Task.andThen (\resultB -> case resultA.userId == resultB.userId of True -> Task.succeed (resultA, resultB) False -> Task.fail “User is not the same” ) )

Стоит отметить, что вместо функции Task.map используется функция Task.andThen и успешность выполнения второй задачи мы определяем сами с помощью функций Задача.

успех И Task.fail .

Если одна из задач может выйти из строя и это допустимо, то нужно использовать функцию Task.onError чтобы указать значение в случае ошибки:

someTaskA |> Task.onError (\msg -> Task,succeed defaultValue) |> Task.andThen (\resultA -> someTaskB |> Task.map (\resultB -> (resultA, resultB) ) )

Вызов функции Task.onError должен быть объявлен сразу после объявления задачи.

Последовательные независимые запросы можно выполнять с помощью функций Task.mapN. Которые позволяют объединить несколько результатов задачи в один.

Первая задача, которая завершается неудачно, прерывает всю цепочку, поэтому используйте функцию Task.onError для значений по умолчанию.

Также обратите внимание на функцию Task.sequence , он позволяет выполнять ряд аналогичных задач.

В текущей реализации языка параллельные задачи не описаны.

Их реализация возможна на уровне приложения или компонента посредством обработки событий в функции обновления.

Вся логика остается на плечах разработчика.

Теги: #JavaScript #frontend #Функциональное программирование #node.js #elm

Вместе с данным постом часто просматривают: