В F #, когда вы хотите обрабатывать извлекаемые ошибки вы почти повсеместно хотите использовать option
или Choice<_,_>
типа. На практике единственная разница между ними заключается в том, что 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.
- Одна функция, содержащая логику для попыток повторной попытки; это «обертывает» первую функцию, которая выполняет фактические запросы.
- Оригинальная основная функция теперь обрабатывает только «настройки» (которые вы можете легко извлечь из
app.config
или web.config
) и распечатать окончательные результаты. Другими словами, он не обращает внимания на логику повторной попытки - вы можете изменить одну строку кода с помощью оператора match
и вместо этого использовать функцию запроса на повторную попытку, если хотите.
Если вы хотите, чтобы вытащить содержимое из нескольких URI, и ждать в течение значительного периода времени (например, 5 минут) между повторами, вы должны изменить логику Повторная попытка использовать очереди приоритета или что-то вместо того, чтобы использовать Thread.Sleep
или Async.Sleep
.
Бесстыдная плагин: моя библиотека ExtCore содержит некоторые вещи, которые значительно облегчают вашу жизнь при создании чего-то подобного, особенно если вы хотите сделать все асинхронным. Самое главное, он обеспечивает рабочий процесс asyncChoice
и collections functions designed to work with it.
Что касается вопроса о передаче параметров (например, тайм-аута повтора и количества повторных попыток) - я не думаю, что существует твердое правило для принятия решения о том, следует ли передавать или жестко кодировать их внутри функция. В большинстве случаев я предпочитаю передавать их, хотя, если у вас есть более чем несколько параметров для перехода, вам лучше создать запись, чтобы держать их все и передавать это вместо этого. Другой подход, который я использовал, - это сделать значения параметров option
, где значения по умолчанию извлекаются из файла конфигурации (хотя вы хотите вытащить их из файла один раз и назначить их в какое-то частное поле, чтобы избежать повторного разбора файл конфигурации каждый раз, когда вы вызываете свою функцию); это упрощает изменение значений по умолчанию, которые вы использовали в вашем коде, но также дает вам возможность переопределять их, когда это необходимо.