如何在 Yesod 中捕获异常而不杀死站点?

Posted

技术标签:

【中文标题】如何在 Yesod 中捕获异常而不杀死站点?【英文标题】:How can I catch exceptions within Yesod without killing the site? 【发布时间】:2012-07-22 01:21:24 【问题描述】:

我是 Yesod 和 Haskell 的新手,所以请多多包涵,但我有一个程序可以接受文件,计算单词并在新的 Yesod 网页上显示结果。我需要一种更优雅地捕获异常的方法。例如,如果它在隐藏文件上运行以下函数:

wordCount :: String -> String -> String

wordCount ('.' : _) _   = error "Cannot count hidden files."
wordCount name contents = "<p>There are <b>" ++ show (length $ words contents) ++ "</b> words in your file <i>" ++ name ++ "</i>.</p>"

我想显示该错误消息,然后显示表单以供用户提交新文件。现在它只是转到一个页面,上面写着“页面加载时与服务器的连接已重置。”

我认为 Yesod 有自己的一组 catch 函数,我可能不得不隐藏它来编写自己的函数,但我不确定它们会在哪里,而且我在任何文档中都找不到对它们的任何引用。如果用户输入了错误的文件,我不希望网站崩溃。

任何帮助将不胜感激。

编辑:感谢您的建议。我意识到以这种方式抛出错误并不是处理错误的最佳方法,但问题实际上是我正在使用通常从终端而不是 Yesod 站点运行的现有 Haskell 代码。我用 wordcount 作为一个小例子,但实际上它是几十个文件,几十个函数和分散的错误抛出。希望我可以捕获这些,而不必完全修改现有代码中的每个函数。如果有人对如何在 Yesod 中捕获异常有任何建议,我将不胜感激。

【问题讨论】:

不要将error 用于此类异常。使用Either ErrorMessage ReturnType 或其他一些错误机制。 关于错误机制,最近有一篇关于 Hackage 上新的 errors 包的不错的博客文章说明了使用 EitherMaybe 等的一些基本错误处理技术。 【参考方案1】:

正如 dflemstr 所说,不要将error 用于此类可恢复的错误。错误机制类似于undefined——它代表一个完全灾难性的错误,例如尚未编写的函数或无限循环。对于您希望永远遇到坏情况的部分函数来说,这很好,但对于预期和必须处理的错误来说,它并不是最佳选择。此外,类型系统不反映您对error 的使用,因此您的函数的使用者无法知道捕获错误。

相反,您应该使用像 MaybeEither 这样的类型来表示这样的错误。例如,你可以这样重写这个函数:

wordCount :: String -> String -> Maybe String
wordCount ('.' : _) _   = Nothing
wordCount name contents = Just $ "<p>There are <b>" ++ show (length $ words contents) ++ "</b> words in your file <i>" ++ name ++ "</i>.</p>"

现在,不是返回 String,而是返回 Maybe String。这是一个可以通过NothingJust String 的类型。 Nothing 代表失败——即函数由于输入无效而失败。 Just 代表成功。

接下来,要“捕捉”错误,您可以只进行模式匹配:

case wordCount fileName contents of
  Just res -> res
  Nothing  -> "Cannot count hidden files!"

Data.Maybe 中还有一些方便的功能,可以让您的代码在常见情况下更整洁。

另一个选项是Either 类型。在Maybe 类型中,Nothing 构造函数不携带任何附加信息。您所知道的是该功能失败。 Either 的行为方式相同,除了“失败”情况确实携带任意附加信息。 “失败”案例称为Left,“成功”案例称为Right。该类型还有两个参数:错误信息的类型和结果的类型。它看起来像这样:

wordCount :: String -> String -> Either String String
wordCount ('.' : _) _   = Left "You cannot count a hidden file!"
wordCount name contents = Right $ "<p>There are <b>" ++ show (length $ words contents) ++ "</b> words in your file <i>" ++ name ++ "</i>.</p>"

然后您可以像Maybe 一样处理结果。在这种情况下,我认为Maybe 更可取,因为该函数只有一种方法会失败。如果有很多可能的错误情况,Either 会是更好的选择。

【讨论】:

以上是关于如何在 Yesod 中捕获异常而不杀死站点?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 AngularJS 中捕获/处理后端异常

如何在 FutureTask 中捕获异常

使用 KeyboardInterrupt 异常捕获 SIGINT 在终端中有效,而不是在脚本中

检测异常未被用户捕获而不重新抛出

c# 从delegate.begininvoke 捕获异常而不调用delegate.endinvoke

打印捕获的异常类型而不打印错误描述