gpt4 book ai didi

f# - 正确处理 WebExceptions?

转载 作者:行者123 更新时间:2023-12-04 18:09:43 24 4
gpt4 key购买 nike

我有以下从 Internet 检索网页的 F# 程序:

open System.Net

[<EntryPoint>]
let main argv =
let mutable pageData : byte[] = [| |]
let fullURI = "http://www.badaddress.xyz"
let wc = new WebClient()
try
pageData <- wc.DownloadData(fullURI)
()
with
| :? System.Net.WebException as err -> printfn "Web error: \n%s" err.Message
| exn -> printfn "Unknown exception:\n%s" exn.Message

0 // return an integer exit code

这工作正常 如果 URI 有效 机器有互联网连接 Web 服务器正确响应等。在理想的函数式编程世界中,函数的结果将不依赖于未作为参数传递的外部变量(副作用)。

我想知道的是什么是合适的 F# 设计模式来处理可能需要函数来处理 的操作。可回收外部错误。例如,如果网站出现故障,可能需要等待 5 分钟再试一次。应该显式传递重试次数和重试之间的延迟等参数,还是可以将这些变量嵌入函数中?

最佳答案

在 F# 中,当您想要处理可恢复的错误时,您几乎普遍希望使用 optionChoice<_,_>类型。实际上,它们之间的唯一区别是 Choice允许您在 option 时返回有关错误的一些信息才不是。换句话说,option当某事失败的方式或原因无关紧要(只是它确实失败了)时,这是最好的; Choice<_,_>当有关于某事失败的方式或原因的信息很重要时使用。例如,您可能希望将错误信息写入日志;或者您可能想根据失败的原因以不同的方式处理错误情况——一个很好的用例是提供准确的错误消息以帮助用户诊断问题。

考虑到这一点,以下是我将如何重构您的代码以以干净、实用的方式处理故障:

open System
open System.Net

/// Retrieves the content at the given URI.
let retrievePage (client : WebClient) (uri : Uri) =
// Preconditions
checkNonNull "uri" uri
if not <| uri.IsAbsoluteUri then
invalidArg "uri" "The URI must be an absolute URI."

try
// If the data is retrieved successfully, return it.
client.DownloadData uri
|> Choice1Of2
with
| :? System.Net.WebException as webExn ->
// Return the URI and WebException so they can be used to diagnose the problem.
Choice2Of2 (uri, webExn)
| _ ->
// Reraise any other exceptions -- we don't want to handle them here.
reraise ()

/// Retrieves the content at the given URI.
/// If a WebException is raised when retrieving the content, the request
/// will be retried up to a specified number of times.
let rec retrievePageRetry (retryWaitTime : TimeSpan) remainingRetries (client : WebClient) (uri : Uri) =
// Preconditions
checkNonNull "uri" uri
if not <| uri.IsAbsoluteUri then
invalidArg "uri" "The URI must be an absolute URI."
elif remainingRetries = 0u then
invalidArg "remainingRetries" "The number of retries must be greater than zero (0)."

// Try to retrieve the page.
match retrievePage client uri with
| Choice1Of2 _ as result ->
// Successfully retrieved the page. Return the result.
result
| Choice2Of2 _ as error ->
// Decrement the number of retries.
let retries = remainingRetries - 1u

// If there are no retries left, return the error along with the URI
// for diagnostic purposes; otherwise, wait a bit and try again.
if retries = 0u then error
else
// NOTE : If this is modified to use 'async', you MUST
// change this to use 'Async.Sleep' here instead!
System.Threading.Thread.Sleep retryWaitTime

// Try retrieving the page again.
retrievePageRetry retryWaitTime retries client uri

[<EntryPoint>]
let main argv =
/// WebClient used for retrieving content.
use wc = new WebClient ()

/// The amount of time to wait before re-attempting to fetch a page.
let retryWaitTime = TimeSpan.FromSeconds 2.0

/// The maximum number of times we'll try to fetch each page.
let maxPageRetries = 3u

/// The URI to fetch.
let fullURI = Uri ("http://www.badaddress.xyz", UriKind.Absolute)

// Fetch the page data.
match retrievePageRetry retryWaitTime maxPageRetries wc fullURI with
| Choice1Of2 pageData ->
printfn "Retrieved %u bytes from: %O" (Array.length pageData) fullURI

0 // Success
| Choice2Of2 (uri, error) ->
printfn "Unable to retrieve the content from: %O" uri
printfn "HTTP Status: (%i) %O" (int error.Status) error.Status
printfn "Message: %s" error.Message

1 // Failure

基本上,我把你的代码分成两个函数,加上原来的 main :
  • 一种尝试从指定 URI 检索内容的函数。
  • 一个包含重试逻辑的函数;这“包装”了执行实际请求的第一个函数。
  • 原来的 main 函数现在只处理“设置”(您可以轻松地从 app.configweb.config 中提取)并打印最终结果。换句话说,它忽略了重试逻辑——您可以使用 match 修改单行代码。如果需要,请使用非重试请求功能。

  • 如果您想从多个 URI 中提取内容并在重试之间等待大量时间(例如,5 分钟),您应该修改重试逻辑以使用优先级队列或其他东西,而不是使用 Thread.SleepAsync.Sleep .

    无耻塞:我的 ExtCore库包含一些东西,可以让你在构建这样的东西时变得更加轻松,特别是如果你想让它全部异步。最重要的是,它提供了 asyncChoice 工作流程和 collections functions designed to work with it .

    至于你关于传入参数的问题(如重试超时和重试次数)——我认为没有一个硬性规则来决定是在函数中传递它们还是硬编码它们。在大多数情况下,我更喜欢传入它们,但如果你有多个参数要传入,你最好创建一个记录来保存它们并传递它。我使用的另一种方法是使参数 option值,其中默认值是从配置文件中提取的(尽管您希望将它们从文件中提取一次并将它们分配给某个私有(private)字段以避免每次调用函数时重新解析配置文件);这使得修改您在代码中使用的默认值变得容易,但也让您在必要时可以灵活地覆盖它们。

    关于f# - 正确处理 WebExceptions?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17381799/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com