Seguimos hablando de Elm 0.18 .
Olmo Cómodo e incómodo
Olmo Cómodo e incómodo. Composición
Olmo Cómodo e incómodo. Json.Encoder y Json.Decoder
En este artículo, consideramos cuestiones de interacción con el lado del servidor.
Ejecución de la consulta
Se pueden encontrar ejemplos de consultas simples en la descripción del paquete Http .
El tipo de solicitud es Http . Solicite a .
El tipo de resultado de la consulta es Result Http.Error a.
Ambos tipos están parametrizados por el tipo de usuario, cuyo decodificador debe especificarse al generar la solicitud.
Puede ejecutar la solicitud utilizando las funciones:
- Http.send;
- Http.toTask.
Http.send le permite ejecutar la solicitud y, una vez finalizada, pasa el mensaje a la función de actualización especificada en el primer argumento. El mensaje lleva datos sobre el resultado de la solicitud.
Http.toTask le permite crear una tarea a partir de una solicitud que puede ejecutar. Usar la función Http.toTask, en mi opinión, es lo más conveniente, ya que las instancias de Tarea se pueden combinar usando varias funciones , por ejemplo, Task.map2 .
Veamos un ejemplo. Por ejemplo, para guardar los datos del usuario, se deben ejecutar dos consultas dependientes consecutivas. Deje que cree una publicación del usuario y le guarde fotos (se usa algo de CDN).
Primero, considere la implementación para el caso Http.Send. Para esto necesitamos dos funciones:
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
Los tipos UserData y CDNData no se describirán; por ejemplo, no son importantes. La función encodeUserData es un codificador. saveImages acepta un identificador de datos de usuario, que se utiliza para formar la dirección, y una lista de fotos. La función imagesBody forma un cuerpo de solicitud del tipo multipart / form-data . Las funciones decodeUserData y decodedCDNData decodifican la respuesta del servidor para los datos del usuario y el resultado de la solicitud CDN, respectivamente.
A continuación, necesitamos dos mensajes, los resultados de la consulta:
type Msg = DataSaved (Result Http.Error UserData) | ImagesSaved (Result Http.Error CDNData)
Supongamos que, en algún lugar de la implementación de la función de actualización, hay una sección de código que almacena datos. Por ejemplo, podría verse así:
update : Msg -> Model -> (Model, Cmd Msg) update msg model case Msg of ClickedSomething -> (model, Http.send DataSaved (save model.userData))
En este caso, se crea una consulta y se marca con un mensaje DataSaved. Además, se recibe este mensaje:
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)
En caso de guardar con éxito, actualizamos los datos en el modelo y llamamos a la solicitud para guardar las fotos donde transferimos el identificador de datos de usuario recibido. Procesar el mensaje ImagesSaved será similar a DataSaved, será necesario manejar casos exitosos y fallidos.
Ahora considere la opción de usar la función Http.toTask. Usando las funciones descritas, definimos una nueva función:
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) } )
Ahora, utilizando la capacidad de combinar tareas, podemos obtener todos los datos en un solo mensaje:
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 ejecutar solicitudes, utilizamos la función Task.attempt , que le permite completar la tarea. No debe confundirse con la función Task.perform . Task.perform: le permite completar tareas que no pueden fallar . Task.attempt: realiza tareas que pueden fallar .
Este enfoque es más compacto en términos de la cantidad de mensajes, la complejidad de la función de actualización y le permite mantener la lógica más local.
En mis proyectos, aplicaciones y componentes, a menudo creo el módulo Commands.elm, en el que describo las funciones de interactuar con la parte del servidor con el tipo ... -> Task Http.Error a.
Solicitar estado de ejecución
En el proceso de ejecución de consultas, la interfaz a menudo tiene que bloquearse total o parcialmente, y también informar errores en la ejecución de consultas, si corresponde. En general, el estado de una solicitud se puede describir como:
- la solicitud no ha sido completada;
- la solicitud está en curso;
- La solicitud se completó con éxito.
- solicitud fallida
Para tal descripción, hay un paquete RemoteData . Al principio, lo usé activamente, pero con el tiempo, la presencia de un tipo adicional de WebData se volvió redundante, y trabajar con él fue tedioso. En lugar de este paquete, aparecieron las siguientes reglas:
- declarar todos los datos del servidor como tal vez. En este caso, Nothing indica la ausencia de datos;
- declare un atributo de carga de tipo Int en la aplicación o modelo de componente. El parámetro almacena el número de solicitudes ejecutadas. El único inconveniente de este enfoque es la necesidad de incrementar y disminuir el atributo al comienzo de la solicitud y al finalizar, respectivamente;
- declarar un atributo de error de tipo Cadena de lista en la aplicación o modelo de componente. Este atributo se usa para almacenar datos de error.
El esquema descrito no es mucho mejor que la opción con el paquete RemoteData, como muestra la práctica. Si alguien tiene otras opciones, comparta en los comentarios.
El estado de ejecución de la solicitud debe incluir el progreso de descarga del paquete Http.Progress .
Secuencia de tareas
Considere las opciones para secuencias de tareas que a menudo se encuentran en el desarrollo:
- tareas dependientes secuenciales;
- tareas independientes consistentes;
- tareas paralelas independientes.
Las tareas dependientes sucesivas ya se han considerado anteriormente; daré una descripción general y enfoques de implementación en esta sección.
La secuencia de tareas se interrumpe en el primer fallo y se devuelve un error. Si tiene éxito, se devuelve una combinación de resultados:
someTaskA |> Task.andThen (\resultA -> someTaskB |> Task.map (\resultB -> (resultA, resultB) ) )
Este código crea una tarea de tipo Error de tarea (a, b), que se puede realizar más tarde.
La función Task.andThen le permite transferir una nueva tarea para su ejecución si la anterior se completa con éxito. La función Task.map le permite convertir resultados de melón de ejecución en caso de éxito.
Hay opciones cuando la finalización exitosa de la tarea no será suficiente y necesita verificar la consistencia de los datos. Suponga que las ID de usuario coinciden:
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 la pena señalar que en lugar de la función Task.map, se utiliza la función Task.andThen y el éxito de la segunda tarea se determina de forma independiente utilizando las funciones Task.succeed y Task.fail .
Si una de las tareas puede fallar y esto es aceptable, debe usar la función Task.onError para indicar el valor en caso de error:
someTaskA |> Task.onError (\msg -> Task,succeed defaultValue) |> Task.andThen (\resultA -> someTaskB |> Task.map (\resultB -> (resultA, resultB) ) )
La llamada a la función Task.onError debe declararse inmediatamente después de declarar la tarea.
Se pueden realizar consultas independientes sucesivas utilizando las funciones Task.mapN. Lo que le permite combinar varios resultados de tareas en uno. La primera tarea caída interrumpe la ejecución de toda la cadena, así que use la función Task.onError para los valores predeterminados. Consulte también la función Task.sequence , que le permite realizar una serie de tareas similares.
No se describen tareas paralelas en la implementación del lenguaje actual. Su implementación es posible a nivel de aplicación o componente a través del procesamiento de eventos en la función de actualización. Toda la lógica permanece sobre los hombros del desarrollador.