【问题标题】:How do I asynchronously download a web page in F# and catch errors如何在 F# 中异步下载网页并捕获错误
【发布时间】:2021-05-01 01:29:17
【问题描述】:

我一直在尝试找出惯用的 F# 方式来异步下载网页并处理任何错误/HTTP 失败代码,我认为这是迄今为止我最接近的尝试,但我在 Choice1Of2 行上遇到类型错误

我愿意

a) 了解失败的原因(我仍在学习 F#)
b)知道这是否是正确的方法/如何使这项工作发挥作用,或者我是否完全走错了路

FS0001    This expression was expected to have type
    'Async<Choice<string,exn>>'    
but here has type
    'Choice<'a,'b>'
    let fetchAsync url = async {
        return! Async.Catch(async {
            let! str = Http.AsyncRequestString(url)
            return str })
    }

    let result = fetchAsync "http://www.example.bad"
    match result with      
    | Choice1Of2 v -> logger.LogInformation("worked")
    | Choice2Of2 ex -> logger.LogInformation("failed")  

【问题讨论】:

    标签: f# f#-data


    【解决方案1】:

    a) 为什么会失败

    您的fetchAsync 函数返回一个Async&lt;Choice&lt;_&gt;&gt;,并且您的模式匹配就好像该函数只返回Choice&lt;_&gt;。不幸的是,你不能在 Async 上进行模式匹配,因为那将是一个阻塞操作,正是 Async 尝试远离的。

    b) 处理这些事情的惯用方式。

    但是,您可以做的是留在异步上下文中并处理其中的故障。 F# 提供(至少)两种常用的方法来处理这个问题。例如。您可以使用允许您编写管道的异步辅助函数:

    let fetchAsyncPipeline (url: string) =
        FSharp.Data.Http.AsyncRequestString(url)
        |> Async.Catch
        |> Async.map (function
            | Choice1Of2 v -> Ok v
            | Choice2Of2 e -> Error e.Message)
    

    不幸的是,Async.map 包含在 not yet 中。但是您可以像这样为自己定义它:

    namespace global
    
    [<RequireQualifiedAccess>]
    module Async =
    
        let map f xA =
            async {
                let! x = xA
                return f x
            }
    

    或者,您可以使用提供语法糖的 F# 的 computation expressions 以更命令式的方式编写上述内容:

    let fetchAsyncCe (url: string) =
        async {
            try
                return!
                    FSharp.Data.Http.AsyncRequestString(url)
                    |> Async.map Ok
                with
                | e -> return Error e.Message
        }
    

    在这两种解决方案中,我都将异常转换为 F# 的 result 类型,我个人认为这是处理错误的最佳方式。

    最后,正如 brianberns 所指出的,您的表达式仅包装在 Async 类型中。但是unlike C# 的任务,异步计算代表程序如何计算某些东西,但是该程序尚未启动,您必须显式运行异步操作。一种方法是使用 Async.RunSynchronously:

    Async.RunSynchronously (fetchAsyncCe "https://fsharpforfunandprofit.com/")
    

    PS:您可能遇到过 Scott Wlaschin 的出色 F# for fun and profit,但如果您没有遇到过,您应该去看看。就个人而言,我发现它是教你 F# 和函数式编程的最佳资源。

    【讨论】:

    • 什么是 Async.map 调用?好像没找到?
    • 嗯,这很尴尬。我已经使用它很长时间了,以至于我忘记了我自己定义了它。我用函数的定义更新了我的答案。
    【解决方案2】:

    这不能编译的原因是因为你从未真正运行fetchAsync 创建的计算。像这样的东西应该可以代替:

    let fetchAsync url =
        Http.AsyncRequestString(url)
            |> Async.Catch
            |> Async.RunSynchronously
    

    【讨论】:

      【解决方案3】:

      几个小时后,我喝了很多咖啡

      let fetchAsync2 (url: string) =
      async { return! Async.Catch(Http.AsyncRequestString(url)) }
      |> Async.RunSynchronously
      
      let result = fetchAsync2 url
      
      match result with
      | Choice1Of2 html -> html
      | Choice2Of2 error -> error.Message
      

      此代码按我想要的方式工作,但我会留下问题以防万一有人有更好的解决方案

      合并我的版本和@brianberns 提供的版本,我认为我最喜欢这个版本?

      let fetchAsync3 (url: string) =
      Http.AsyncRequestString(url)
      |> Async.Catch 
      |> Async.RunSynchronously
      
      let result = fetchAsync3 url
      
      match result with
      | Choice1Of2 html -> html
      | Choice2Of2 error -> error.Message
      

      【讨论】:

      • 一个小评论:对于任何计算构建器cb,表达式cb { return! func() }cb { let! foo = func(); return foo } 与单独的func() 相同。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-05-31
      • 2016-08-23
      • 2018-09-04
      • 2022-01-14
      相关资源
      最近更新 更多