You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Elm调用Trefle API获取植物图片失败,请求代码排查

问题排查与修复方案

1. 错误信息缺失导致无法定位问题

当前代码直接忽略了Http.Error的具体细节,这是你看不到错误原因、甚至尝试获取详细错误时崩溃的核心原因。添加错误转字符串的辅助函数,把具体错误信息展示出来:

httpErrorToString : Http.Error -> String
httpErrorToString error =
    case error of
        Http.BadUrl url ->
            "无效URL: " ++ url
        Http.Timeout ->
            "请求超时"
        Http.NetworkError ->
            "网络连接错误"
        Http.BadStatus status ->
            "请求失败,状态码: " ++ String.fromInt status
        Http.BadBody errMsg ->
            "JSON解码失败: " ++ errMsg

然后修改ReceiveImage的错误分支:

Err httpError ->
    ( { model | error = httpErrorToString httpError }, Cmd.none )

现在你能看到具体错误类型,比如令牌无效(401)、植物未找到(404)或解码失败的具体原因。

2. JSON解码逻辑存在隐患

Trefle API的image_url是可选字段(部分植物无图片),且你当前的解码器未处理数组为空或字段为null的情况,会直接触发解码失败。修改解码器,用maybe处理可选字段:

decodeImageUrl : Decoder String
decodeImageUrl =
    field "data" (list (field "image_url" (Json.Decode.maybe string)))
        |> Json.Decode.map
            (\results ->
                results
                    |> List.head
                    |> Maybe.andThen identity
                    |> Maybe.withDefault ""
            )

3. URL未编码导致特殊字符搜索失败

直接拼接搜索词到URL会导致空格、中文或特殊字符(如&、?)破坏URL结构,使用Http.encodeUri编码搜索词:

url =
    "https://trefle.io/api/v1/plants/search?token=MY_API_TOKEN&q=" ++ Http.encodeUri searchTerm

4. 额外优化:拦截空搜索请求

在发起请求前判断搜索词是否为空,避免无效请求:

FetchImage ->
    if String.isEmpty model.searchTerm then
        ( { model | error = "请输入植物名称" }, Cmd.none )
    else
        ( model, fetchPlantImage model.searchTerm )

完整修复后的代码

module Main exposing (..)

import Browser
import Html exposing (Html, button, div, img, input, text)
import Html.Attributes exposing (placeholder, src, type_)
import Html.Events exposing (onClick, onInput)
import Http
import Json.Decode exposing (Decoder, field, list, maybe, string)
import Maybe exposing (withDefault, andThen)


type alias Model =
    { searchTerm : String
    , imageUrl : String
    , error : String
    }


init : () -> ( Model, Cmd Msg )
init _ =
    ( { searchTerm = "", imageUrl = "", error = "" }, Cmd.none )


type Msg
    = UpdateSearchTerm String
    | FetchImage
    | ReceiveImage (Result Http.Error String)


httpErrorToString : Http.Error -> String
httpErrorToString error =
    case error of
        Http.BadUrl url ->
            "无效URL: " ++ url
        Http.Timeout ->
            "请求超时"
        Http.NetworkError ->
            "网络连接错误"
        Http.BadStatus status ->
            "请求失败,状态码: " ++ String.fromInt status
        Http.BadBody errMsg ->
            "JSON解码失败: " ++ errMsg


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        UpdateSearchTerm term ->
            ( { model | searchTerm = term }, Cmd.none )

        FetchImage ->
            if String.isEmpty model.searchTerm then
                ( { model | error = "请输入植物名称" }, Cmd.none )
            else
                ( model, fetchPlantImage model.searchTerm )

        ReceiveImage result ->
            case result of
                Ok url ->
                    ( { model | imageUrl = url, error = "" }, Cmd.none )

                Err httpError ->
                    ( { model | error = httpErrorToString httpError }, Cmd.none )


view : Model -> Html Msg
view model =
    div []
        [ input [ placeholder "Enter plant name", onInput UpdateSearchTerm ] []
        , button [ onClick FetchImage ] [ text "Search" ]
        , if model.imageUrl /= "" then
            img [ src model.imageUrl ] []
          else
            text ""
        , if model.error /= "" then
            div [] [ text model.error ]
          else
            text ""
        ]


fetchPlantImage : String -> Cmd Msg
fetchPlantImage searchTerm =
    let
        url =
            "https://trefle.io/api/v1/plants/search?token=MY_API_TOKEN&q=" ++ Http.encodeUri searchTerm

        decodeImageUrl : Decoder String
        decodeImageUrl =
            field "data" (list (field "image_url" (maybe string)))
                |> Json.Decode.map
                    (\results ->
                        results
                            |> List.head
                            |> andThen identity
                            |> withDefault ""
                    )
    in
    Http.get
        { url = url
        , expect = Http.expectJson ReceiveImage decodeImageUrl
        }


main : Program () Model Msg
main =
    Browser.element
        { init = init
        , update = update
        , subscriptions = \_ -> Sub.none
        , view = view
        }

内容的提问来源于stack exchange,提问作者lani2000

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 06:13:10