elmelm-architecture

Sequence Http.get in Elm


Below I have a button that attempts to load remote content ...

import Post exposing (Post)
import Html exposing (..)
import Html.Events exposing (..)
import Http
import Json.Decode as Decode


type alias Model =
    { posts : List Post }


type Msg
    = Search String
    | PostsReceived (Result Http.Error (List Post))


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        Search s ->
            let
                cmd =
                    (Decode.list Post.decode)
                        |> Http.get ("/posts?author=" ++ s)
                        |> Http.send PostsReceived
            in
                ( model, cmd )

        PostsReceived (Ok posts) ->
            { model | posts = posts }
                ! []

        PostsReceived (Err error) ->
            ( model, Cmd.none )


view : Model -> Html Msg
view model =
    button
        [ onClick (Search "amelia") ]
        [ text "Read posts by Amelia" ]

This is a valid Elm program, only there's one little problem: The API doesn't allow me to search by string. This is not allowed

/posts?author=amelia  => Malformed Request Error

However, this is allowed

/posts?author=2       => [ {...}, {...}, ... ]

So I must first fetch an author to get his/her id, and then I can fetch posts using the author's id...

/author?name=amelia => { id: 2, name: "amelia", ... }
/posts?author=2

How can I sequence one request after the next? Ideally I'd like to cache the authors somewhere in the model so we're only requesting ones that we haven't seen before.


Solution

  • You can use Task.andThen to chain two tasks together. Assuming that the /posts response includes the author ID, you can then add that author ID into you model when you handle the response.

        Search s ->
            let
                getAuthor =
                    Author.decode
                        |> Http.get ("/author?name=" ++ s)
                        |> Http.toTask
                getPosts author =
                    (Decode.list Post.decode)
                        |> Http.get ("/posts?author=" ++ author.id)
                        |> Http.toTask
                cmd =
                    getAuthor
                        |> Task.andThen getPosts
                        |> Task.attempt PostsReceived
            in
                ( model, cmd )
    

    I've got this compiling at https://ellie-app.com/DBJc6Kn3G6a1 if that helps