Elm. Confortável e estranho. Http, Tarefa

Continuamos a falar sobre o olmo 0,18 .


Elm. Confortável e desajeitado
Elm. Confortável e estranho. Composição:
Elm. Confortável e estranho. Json.Encoder e Json.Decoder


Neste artigo, consideramos problemas de interação com o lado do servidor.


Execução de consulta


Exemplos de consultas simples podem ser encontrados na descrição do pacote Http .


O tipo de solicitação é Http.Request a .
O tipo de resultado da consulta é Result Http.Error a.
Ambos os tipos são parametrizados pelo tipo de usuário, cujo decodificador deve ser especificado ao gerar a solicitação.


Você pode executar a solicitação usando as funções:


  1. Http.send;
  2. Http.toTask.

Http.send permite executar a solicitação e, após a conclusão, passa a mensagem para a função de atualização especificada no primeiro argumento. A mensagem carrega dados sobre o resultado da solicitação.


Http.toTask permite criar uma tarefa a partir de uma solicitação que você pode executar. Usar a função Http.toTask, na minha opinião, é o mais conveniente, pois as instâncias de tarefas podem ser combinadas usando várias funções , por exemplo, Task.map2 .


Vejamos um exemplo. Por exemplo, para salvar dados do usuário, duas consultas dependentes consecutivas devem ser executadas. Deixe que ele crie uma postagem do usuário e salve as fotos para ele (é usada alguma CDN).


Primeiro, considere a implementação do caso Http.Send. Para isso, precisamos de duas funções:


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 

Os tipos UserData e CDNData não serão descritos; por exemplo, eles não são importantes. A função encodeUserData é um codificador. O saveImages aceita um identificador de dados do usuário, usado para formar o endereço, e uma lista de fotos. A função imagesBody forma um corpo de solicitação do tipo multipart / data-form . As funções decodeUserData e decodedCDNData decodificam a resposta do servidor para dados do usuário e o resultado da solicitação CDN, respectivamente.


Em seguida, precisamos de duas mensagens, os resultados da consulta:


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

Suponha que, em algum ponto da implementação da função de atualização, haja uma seção de código que armazene dados. Por exemplo, pode ser assim:


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

Nesse caso, uma consulta é criada e marcada com uma mensagem DataSaved. Além disso, esta mensagem é recebida:


 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) 

No caso de uma economia bem-sucedida, atualizamos os dados no modelo e chamamos a solicitação para salvar as fotos para onde transferimos o identificador de dados do usuário recebido. O processamento da mensagem ImagesSaved será semelhante ao DataSaved; será necessário lidar com casos bem-sucedidos e com falha.


Agora considere a opção de usar a função Http.toTask. Usando as funções descritas, definimos uma nova função:


 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) } ) 

Agora, usando a capacidade de combinar tarefas, podemos obter todos os dados em uma mensagem:


 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) 

Para executar solicitações, usamos a função Task.attempt , que permite concluir a tarefa. Não deve ser confundido com a função Task.perform . Task.perform - permite concluir tarefas que não podem falhar . Task.attempt - Executa tarefas que podem falhar .


Essa abordagem é mais compacta em termos de número de mensagens, complexidade da função de atualização e permite manter a lógica mais local.


Nos meus projetos, aplicativos e componentes, costumo criar o módulo Commands.elm, no qual descrevo as funções de interação com a parte do servidor com o tipo ... -> Task Http.Error a.


Status de execução da solicitação


No processo de execução de consultas, a interface geralmente precisa ser bloqueada no todo ou em parte, além de relatar erros na execução de consultas, se houver. Em geral, o status de uma solicitação pode ser descrito como:


  1. a solicitação não foi concluída;
  2. a solicitação está em andamento;
  3. A solicitação foi concluída com sucesso.
  4. solicitação falhou.

Para essa descrição, há um pacote RemoteData . No começo, eu o usei ativamente, mas com o tempo, a presença de um tipo adicional de WebData se tornou redundante e o trabalho com ele foi entediante. Em vez deste pacote, as seguintes regras apareceram:


  1. declare todos os dados do servidor como Talvez. Nesse caso, Nothing, indica a ausência de dados;
  2. declare um atributo de carregamento do tipo Int no modelo de aplicativo ou componente. O parâmetro armazena o número de solicitações executadas. O único inconveniente dessa abordagem é a necessidade de aumentar e diminuir o atributo no início da solicitação e na conclusão, respectivamente;
  3. declare um atributo de erro do tipo List String no modelo de aplicativo ou componente. Este atributo é usado para armazenar dados de erro.

O esquema descrito não é muito melhor do que a opção com o pacote RemoteData, como mostra a prática. Se alguém tiver outras opções, compartilhe nos comentários.


O status de execução da solicitação deve incluir o progresso do download do pacote Http.Progress .


Sequência de tarefas


Considere as opções para sequências de tarefas que geralmente são encontradas no desenvolvimento:


  1. tarefas dependentes sequenciais;
  2. tarefas independentes consistentes;
  3. tarefas independentes paralelas.

Tarefas dependentes sucessivas já foram consideradas acima; darei uma descrição geral e abordagens para implementação nesta seção.


A sequência de tarefas é interrompida na primeira falha e um erro é retornado. Se for bem-sucedido, alguma combinação de resultados será retornada:


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

Este código cria uma tarefa do tipo Erro de tarefa (a, b), que pode ser executado posteriormente.


A função Task.andThen permite transferir uma nova tarefa a ser executada em caso de conclusão bem-sucedida da anterior. A função Task.map permite converter resultados de execução de melão em caso de sucesso.


Existem opções em que a conclusão bem-sucedida da tarefa não será suficiente e é necessário verificar a consistência dos dados. Suponha que os IDs de usuário correspondam:


 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” ) ) 

Vale ressaltar que, em vez da função Task.map, a função Task.andThen é usada e o sucesso da segunda tarefa é determinado independentemente, usando as funções Task.succeed e Task.fail .


Se uma das tarefas puder falhar e isso for aceitável, você precisará usar a função Task.onError para indicar o valor em caso de erro:


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

A chamada para a função Task.onError deve ser declarada imediatamente após a tarefa ser declarada.


Consultas independentes sucessivas podem ser executadas usando as funções Task.mapN. O que permite combinar vários resultados de tarefas em um. A primeira tarefa interrompida interrompe a execução de toda a cadeia, portanto, use a função Task.onError para os valores padrão. Verifique também a função Task.sequence , que permite executar uma série de tarefas semelhantes.


Tarefas paralelas na implementação do idioma atual não são descritas. Sua implementação é possível no nível do aplicativo ou componente através do processamento de eventos na função de atualização. Toda a lógica permanece nos ombros do desenvolvedor.

Source: https://habr.com/ru/post/pt424979/


All Articles