Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 677
Mulan Avatar asked Jun 29 '18 00:06

Mulan


3 Answers

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

like image 189
bdukes Avatar answered Oct 26 '22 12:10

bdukes


You can chain together tasks using Task.andThen. You'll first have to convert the web requests to tasks using Http.toTask:

postsByAuthorName : String -> Cmd Msg
postsByAuthorName name =
    Http.get ("/author?name=" ++ name) (Decode.field "id" Decode.int)
        |> Http.toTask
        |> Task.andThen (\id ->
            Http.get ("/posts?author=" ++ toString id) (Decode.list decodePost)
                |> Http.toTask)
        |> Task.attempt PostsReceived
like image 32
Chad Gilbert Avatar answered Oct 26 '22 12:10

Chad Gilbert


A a dictionary and a couple more Msg options should do it. You'll have to write the decoder for the Author response, but other than that this should work

type alias Model =
    { posts : List Post
    , authors : Dict String Int }


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


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

                Just num -> 
                    let
                        cmd =
                            (Decode.list Author.decode)
                                |> Http.get ("/posts?author=" ++ num)
                                |> Http.send PostsReceived
                    in
                        ( model, cmd )

        AuthorReceived (Ok number name) ->
            let
                updatedAuthors = Dict.inster name number model.authors
                cmd =
                    (Decode.list Post.decode)
                        |> Http.get ("/posts?author=" ++ number)
                        |> Http.send PostsReceived 
            in
                {model | authors = updatedAuthors } ! [cmd]

        AuthorReceived (Err error) ->
            (mode, Cmd.none )

        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" ]
like image 2
Jolanda Avatar answered Oct 26 '22 10:10

Jolanda