2016-01-27 55 views
13

におけるHTTPリクエストの結果を抽出する方法:httpリクエストを作成することが可能であるエルムの<a href="http://package.elm-lang.org/packages/evancz/elm-html/4.0.2/Html">html</a>パッケージを使用エルム

https://api.github.com/users/nytimes/repos 

これらは、GitHubの上のすべてのNew York Timesリポジトリです。基本的に2つの私はGithubの応答からしたいと思う項目、ID

[ { "id": 5803599, "name": "backbone.stickit" , ... }, 
    { "id": 21172032, "name": "collectd-rabbitmq" , ... }, 
    { "id": 698445, "name": "document-viewer" , ... }, ... ] 

があるHttp.getためのエルムの種類は、私はないJSON Decoderオブジェクト

> Http.get 
<function> : Json.Decode.Decoder a -> String -> Task.Task Http.Error a 

が必要ですまだリストを開く方法を知っている。だから私はデコーダJson.Decode.stringと少なくとも一致した型を入れましたが、私はtaskオブジェクトで何をするのか分かりませんでした。

> tsk = Http.get (Json.Decode.list Json.Decode.string) url 
{ tag = "AndThen", task = { tag = "Catch", task = { tag = "Async", asyncFunction = <function> }, callback = <function> }, callback = <function> } 
    : Task.Task Http.Error (List String) 

> Task.toResult tsk 
{ tag = "Catch", task = { tag = "AndThen", task = { tag = "AndThen", task = { tag = "Catch", task = { tag = "Async", asyncFunction = <function> }, callback = <function> }, callback = <function> }, callback = <function> }, callback = <function> } 
    : Task.Task a (Result.Result Http.Error (List String)) 

私はいくつかのdiv要素で表示することができますが、私もデータを得ることができないので、私はちょうどレポ名のエルムオブジェクトをしたいです。


誰かゆっくりは、デコーダを記述する方法と、エルムとからデータを取得する方法を通じて、私を歩くことができますか?エルム0.17のための

答えて

24

更新:

私はエルム0.17で動作するように、この答えの完全な要点を更新しました。あなたはsee the full source code hereです。それはhttp://elm-lang.org/tryで実行されます。

0.17でいくつかの言語とAPIの変更が行われ、以下の推奨事項の一部が廃止されました。あなたはread about the 0.17 upgrade plan hereです。

私は以下の0.16の元の回答を残しますが、compare the final gists to see a list of what has changedとすることができます。私は新しい0.17バージョンがよりクリーンで理解しやすいと信じています。エルム0.16のための

オリジナル回答:

あなたはエルムREPLを使用しているように見えます。 As noted hereでは、REPLでタスクを実行することはできません。 については、と少し詳しく説明します。代わりに、実際のElmプロジェクトを作成しましょう。

標準Elm toolsをダウンロードしたと仮定しています。

まず、プロジェクトフォルダを作成し、端末で開く必要があります。

Elmプロジェクトを開始する一般的な方法は、StartAppを使用することです。これを出発点として使用しましょう。必要なパッケージをインストールするには、まずElmパッケージマネージャーコマンドラインツールを使用する必要があります。プロジェクトのルートにあるターミナルで以下を実行します。

elm package install -y evancz/elm-html 
elm package install -y evancz/elm-effects 
elm package install -y evancz/elm-http 
elm package install -y evancz/start-app 

、Main.elmと呼ばれるプロジェクトのルートにファイルを作成します。ここにあなたを始めるための定型的なStartAppコードがあります。この質問は特にタスクに関するものなので、ここでは詳細を説明しません。詳しくはElm Architecture Tutorialをご覧ください。今のところ、これをMain.elmにコピーしてください。

import Html exposing (..) 
import Html.Events exposing (..) 
import Html.Attributes exposing (..) 
import Html.Attributes exposing (..) 
import Http 
import StartApp 
import Task exposing (Task) 
import Effects exposing (Effects, Never) 
import Json.Decode as Json exposing ((:=)) 

type Action 
    = NoOp 

type alias Model = 
    { message : String } 

app = StartApp.start 
    { init = init 
    , update = update 
    , view = view 
    , inputs = [ ] 
    } 

main = app.html 

port tasks : Signal (Task.Task Effects.Never()) 
port tasks = app.tasks 

init = 
    ({ message = "Hello, Elm!" }, Effects.none) 

update action model = 
    case action of 
    NoOp -> 
     (model, Effects.none) 

view : Signal.Address Action -> Model -> Html 
view address model = 
    div [] 
    [ div [] [ text model.message ] 
    ] 

これで、elm-reactorを使用してこのコードを実行できます。これは、デフォルトでポート8000​​上のWebサーバーを実行すると、あなたが「こんにちは見るためにMain.elmに移動し、その後、お使いのブラウザでhttp://localhost:8000をプルアップすることができ、プロジェクトフォルダ内の端末に移動し、

elm reactor 

を入力します。 、エルム "の例。

最終目標は、クリックすると、nytimesリポジトリのリストをプルインし、それぞれのIDと名前をリストするボタンを作成することです。最初にそのボタンを作成しましょう。標準のhtml生成関数を使用してこれを行います。このようなものでview機能を更新します。独自のオン

view address model = 
    div [] 
    [ div [] [ text model.message ] 
    , button [] [ text "Click to load nytimes repositories" ] 
    ] 

、ボタンのクリックは何もしません。私たちは、update関数によって処理されるアクションを作成する必要があります。ボタンが開始している動作は、Githubエンドポイントからデータを取得することです。 Actionは現在、次のようになります。

type Action 
    = NoOp 
    | FetchData 

そして、私たちは今、そのようupdate機能では、このアクションの取り扱いをスタブアウトすることができます。今のところ、のは、ボタンのクリックが処理されたことを示すメッセージを変更してみましょう:

update action model = 
    case action of 
    NoOp -> 
     (model, Effects.none) 
    FetchData -> 
     ({ model | message = "Initiating data fetch!" }, Effects.none) 

最後に、我々はボタンがその新しいアクションをトリガーするためにクリック引き起こすことがあります。これは、ボタンのクリックイベントハンドラを生成するonClick関数を使用して行われます。ボタンのHTML生成ラインは次のようになります:

button [ onClick address FetchData ] [ text "Click to load nytimes repositories" ] 

素晴らしい!今すぐメッセージをクリックすると更新されるはずです。 Tasksに移りましょう。

前述のとおり、REPLは(まだ)タスクの呼び出しをサポートしていません。これはJavascriptのような命令的言語から来ている場合、「直感的ではないように思えるかもしれません。」というコードを書くと、すぐにHTTPリクエストが作成されます。エルムのような純粋に機能的な言語では、あなたは少し違ったやり方をします。 Elmでタスクを作成するときは、実際にあなたの意図を示すだけで、副作用を引き起こす何かをするために実行時に渡すことができる一種の「パッケージ」を作成します。この場合、外部の世界に連絡し、URLからデータを取得してください。

URLからデータを取得するタスクを作成してみましょう。まず、私たちが気にするデータの形を表すためにElmの中に型が必要です。 idフィールドとnameフィールドだけを必要としていることを示しました。エルム内部のタイプの構造に関する注意点としては

type alias RepoInfo = 
    { id : Int 
    , name : String 
    } 

、のは分間停止し、我々はRepoInfoインスタンスを作成する方法について話しましょう。 2つのフィールドがあるので、RepoInfoを2つの方法のいずれかで構築することができます。次の2つのステートメントは等価です:私たちはJSONデコードについて話すときに、第2のインスタンスを構築した

-- This creates a record using record syntax construction 
{ id = 123, name = "example" } 

-- This creates an equivalent record using RepoInfo as a constructor with two args 
RepoInfo 123 "example" 

がより重要になります。

これらのリストをモデルに追加しましょう。 init機能を変更して、空のリストから開始する必要があります。

type alias Model = 
    { message : String 
    , repos : List RepoInfo 
    } 

init = 
    let 
    model = 
     { message = "Hello, Elm!" 
     , repos = [] 
     } 
    in 
    (model, Effects.none) 

URLからデータをJSON形式で戻ってくるので、私たちは、タイプセーフなエルムのクラスに生のJSONを変換するために、JSONデコーダが必要になります。次のデコーダを作成します。

repoInfoDecoder : Json.Decoder RepoInfo 
repoInfoDecoder = 
    Json.object2 
    RepoInfo 
    ("id" := Json.int) 
    ("name" := Json.string) 

それを選んでみましょう。デコーダは、未加工のJSONをマッピングするタイプのシェイプにマップするものです。この場合、タイプは2つのフィールドを持つ単純なレコードエイリアスです。私は数ステップ前に、RepoInfoを2つのパラメータを取る関数として使用してRepoInfoインスタンスを作成できることを覚えましたか?そのため、デコーダを作成するのにJson.object2を使用しています。最初の引数がobjectになるのは、2つの引数そのものをとる関数なので、RepoInfoに渡しています。これは、2つの要素を持つ関数に相当します。

残りの引数は、型の形を綴ります。我々のRepoInfoモデルは最初にidname秒をリストしているので、それはデコーダが引数を期待する順序です。

RepoInfoインスタンスのリストをデコードするには別のデコーダが必要です。

repoInfoListDecoder : Json.Decoder (List RepoInfo) 
repoInfoListDecoder = 
    Json.list repoInfoDecoder 

モデルとデコーダが完成したので、データを取得するタスクを返す関数を作成できます。これは実際にデータをフェッチしているわけではなく、後でランタイムに引き渡すことができる関数を作成していることに留意してください。

fetchData : Task Http.Error (List RepoInfo) 
fetchData = 
    Http.get repoInfoListDecoder "https://api.github.com/users/nytimes/repos" 

さまざまなエラーが発生する可能性があります。 Task.toResultを選択して、リクエストの結果をResultタイプにマッピングします。これは少しでも簡単にできます。この例では十分です。私は、タスクのエラー値のための私の型注釈でxを使用してい

fetchData : Task x (Result Http.Error (List RepoInfo)) 
fetchData = 
    Http.get repoInfoListDecoder "https://api.github.com/users/nytimes/repos" 
    |> Task.toResult 

注:これまでそのfetchData署名を変更してみましょう。これは、Resultにマッピングすることで、タスクのエラーに気にする必要がないからです。

ここでは、2つの可能な結果を​​処理するためのアクションが必要になります.HTTPエラーまたは成功した結果。これでActionを更新します。

type Action 
    = NoOp 
    | FetchData 
    | ErrorOccurred String 
    | DataFetched (List RepoInfo) 

あなたの更新機能は、現在のモデルにそれらの値を設定する必要があります。

update action model = 
    case action of 
    NoOp -> 
     (model, Effects.none) 
    FetchData -> 
     ({ model | message = "Initiating data fetch!" }, Effects.none) 
    ErrorOccurred errorMessage -> 
     ({ model | message = "Oops! An error occurred: " ++ errorMessage }, Effects.none) 
    DataFetched repos -> 
     ({ model | repos = repos, message = "The data has been fetched!" }, Effects.none) 

は今、私たちは、これらの新しいアクションのいずれかにResultタスクをマッピングする方法が必要です。私は、エラー処理に行き詰まるしたくないので、私はデバッグ目的私たちにnever-をマップする方法を提供します

httpResultToAction : Result Http.Error (List RepoInfo) -> Action 
httpResultToAction result = 
    case result of 
    Ok repos -> 
     DataFetched repos 
    Err err -> 
     ErrorOccurred (toString err) 

の文字列にエラーオブジェクトを変更するtoStringを使用するつもりです失敗したタスクをアクションに追加します。しかし、StartAppはTasks(と他のいくつかのもの)の上に薄い層であるEffectsを扱います。私たちはそれをすべて一緒に結びつけることができるようになるにはもう少し必要です。それは決して失敗しないHTTPタスクをタイプアクションのエフェクトにマッピングする方法です。

fetchDataAsEffects : Effects Action 
fetchDataAsEffects = 
    fetchData 
    |> Task.map httpResultToAction 
    |> Effects.task 

私はこのことを「決して失敗しない」と知っているかもしれません。最初は混乱していたので説明しよう。タスクを作成すると、結果は保証されますが、成功または失敗です。 Elmアプリケーションを可能な限り堅牢にするために、すべてのケースを明示的に処理することによって、エラーの可能性を取り除きます(私は主に、未処理のJavascript例外を意味します)。そのため、最初にResultにマッピングし、次にエラーメッセージを明示的に処理するActionにマッピングする手間がありました。それが決して失敗しないと言うのは、HTTPの問題が起こり得ないということではなく、すべての可能な結果を​​処理していると言います。エラーは、それらを有効なアクションにマッピングすることによって「成功」にマッピングされます。

私たちの最後のステップの前に、私たちのviewがリポジトリのリストを表示できるようにしましょう。

view : Signal.Address Action -> Model -> Html 
view address model = 
    let 
    showRepo repo = 
     li [] 
     [ text ("Repository ID: " ++ (toString repo.id) ++ "; ") 
     , text ("Repository Name: " ++ repo.name) 
     ] 
    in 
    div [] 
     [ div [] [ text model.message ] 
     , button [ onClick address FetchData ] [ text "Click to load nytimes repositories" ] 
     , ul [] (List.map showRepo model.repos) 
     ] 

最後に、私たちのupdate機能のFetchData場合は、私たちのタスクを開始エフェクトを返すようにすることですすべて一緒にこれを結びつける作品。次のようなケースステートメントを更新してください。

FetchData -> 
    ({ model | message = "Initiating data fetch!" }, fetchDataAsEffects) 

これだけです! elm reactorを実行してボタンをクリックすると、リポジトリのリストを取得できます。エラー処理をテストする場合は、Http.getリクエストのURLを変更して、何が起こるかを確認するだけです。

私はこのas a gistの実例を掲載しました。ローカルで実行したくない場合は、そのコードをhttp://elm-lang.org/tryに貼り付けることで、最終結果を確認することができます。

私は途中で各ステップについて非常に明示的かつ簡潔にしようとしました。典型的なElmアプリケーションでは、これらのステップの多くが数行に集約され、より慣用的な略語が使用されます。私はできるだけ小さく、明示的に物事を作ることによって、あなたがそれらのハードルを惜しみなくしようとしました。私はこれが助けて欲しい!

+1

これは私がエルムを学ぶために進める答えの一種です。最初は理解するのが難しく、脳をねじることができます0.16 - > 0.17アップデートは痛いです...しかし、コミュニティは本当に本当に親切で新しい人に開かれているようです。この回答を書いた時間をありがとう、ありがとうございました。 – gbarillot

関連する問題