【问题标题】:Type mismatch in Play controller action when recovering a future恢复未来时播放控制器操作中的类型不匹配
【发布时间】:2017-08-22 04:41:00
【问题描述】:

我在 scala 播放控制器方法中返回正确类型时遇到问题,有人可以在这里给我提示吗?我正在使用 for comprehantion 处理两个返回 Future 的服务方法,我想优雅地处理结果和错误。

这样做的最佳做法是什么?

 def registerUser = Action { implicit request =>
    Logger.info("Start play actoin")

    RegisterForm.form.bindFromRequest.fold(
      formWithErrors => {
        BadRequest(views.html.register(formWithErrors))
      },
      formData => {

        val registerResult = for {
          reCaptchaOk <- registerUserService.checkRecaptcha(formData.gRecaptchaResponse)
          userId <- registerUserService.registerUser(formData) if reCaptchaOk
        } yield userId

        registerResult.map(
          result => Redirect(routes.DashboardController.dashboard).withSession("USER_ID" -> result.toString))
        .recover{
          e => handleRegisterError(e)
        }

      })

  }

  def handleRegisterError(cause: Throwable)(implicit request: Request[_]) : Result = {
    val form = RegisterForm.form.bindFromRequest
    cause match {
      case dae: DataAccessException =>
        val globalError = dae.getCause.asInstanceOf[PSQLException].getSQLState match {
          case "23505" => GlobalMessages(Seq(GlobalMessage(Messages("errors.db.userAlreadyExists") ,ERROR)))
          case _ => GlobalMessages(Seq(GlobalMessage(Messages("errors.system.error"),ERROR)))
        }
        BadRequest(views.html.register(form,globalError))
      case _ =>
        BadRequest(views.html.register(form))
    }

错误:

[error] (compile:compileIncremental) Compilation failed
[info] Compiling 1 Scala source to C:\repos\scala\SocerGladiatorWeb\target\scala-2.11\classes...
[error] C:\repos\scala\SocerGladiatorWeb\app\controllers\RegisterController.scala:56: type mismatch;
[error]  found   : Throwable => play.api.mvc.Result
[error]  required: PartialFunction[Throwable,?]
[error]           e => handleRegisterError(e)
[error]             ^
[error] one error found
[error] (compile:compileIncremental) Compilation failed

【问题讨论】:

    标签: scala playframework controller future partialfunction


    【解决方案1】:

    简答

    您需要一个部分函数来恢复未来的故障:

        def handleRegisterError(implicit request: Request[_]): PartialFunction[Throwable, Result] = {
          case dae: DataAccessException =>
            val form = RegisterForm.form.bindFromRequest
            val globalError = dae.getCause.asInstanceOf[PSQLException].getSQLState match {
              case "23505" => GlobalMessages(Seq(GlobalMessage(Messages("errors.db.userAlreadyExists"), ERROR)))
              case _ => GlobalMessages(Seq(GlobalMessage(Messages("errors.system.error"), ERROR)))
            }
            BadRequest(views.html.register(form, globalError))
          case _ =>
            val form = RegisterForm.form.bindFromRequest
            BadRequest(views.html.register(form))
        }
    

    然后将控制器代码更改为

      registerResult
        .map { result => 
          Redirect(routes.DashboardController.dashboard).withSession("USER_ID" -> result.toString)
        }
        .recover { 
          handleRegisterError
        }
    

    另请注意,您需要一个异步操作,即

    def registerUser = Action.async { implicit request =>
      ...
    }
    

    因为您返回的不是Result,而是Future[Result]。您可以在Play docs 中找到有关操作的更多信息。

    详情

    如果您查看Futurerecover 方法的文档(请参阅here),您会发现它需要pf: PartialFunction[Throwable, U]

    部分函数与普通函数一样,但它们可能会拒绝某些值(例如,recover 方法不接受所有异常,而只接受主体中指定的异常)。 定义偏函数需要特殊的语法。它非常类似于模式匹配,但没有匹配表达式。

    Future(someAsyncWork).recover {
        case my: MyException => ....
        case _ => ....
    }
    

    这里我们使用内联的部分恢复函数,因此类型将被自动推断,但如果您想将恢复定义为单独的函数,您需要显式声明其类型。

    高级

    部分函数语法(不带match 关键字的模式匹配)在大多数情况下非常简洁和方便,但有时您需要的不止这些。

    例如,请注意,使用这种语法,我们必须在恢复函数中复制部分代码 (val form = RegisterForm.form.bindFromRequest)。

    虽然在您的情况下可能有更好的解决方案,但您始终可以将普通函数转换为部分函数。首先您需要定义一个Throwable =&gt; Option[Result] 类型的函数,然后您可以使用Function#unlift 将其转换为所需的部分函数。 您也可以直接从PartialFunction 继承并实现它的两个方法(applyisDefinedAt)。

    【讨论】:

    • 我也想分享我的经验,尝试在一个地方恢复所有故障,这会导致代码更清晰,然后您必须定义有意义的异常,并在类似情况下重用它们。 Play 让您 handle all errors in one place,因此您无需在所有控制器操作中重复 .recover(...)
    • 这会很好,但是如果你让说来自不同服务方法的重复插入异常具有不同的错误描述,并希望向用户显示该错误......顺便说一句,我认为我会接受你的回答对我来说看起来不错......我是新来玩和 scala,我感谢所有反馈
    • 你可以在异常中携带消息,或者实际上是一个错误列表,异常应该携带有意义的信息(异常物化的想法正是出于这个原因),然后在你的全局错误处理程序中你可以捕获这些错误并将它们发送给客户端。服务层类负责返回带有正确错误列表的异常(失败)。
    • 全局错误处理程序是什么意思...您的意思是根本不处理控制器操作中的错误,而是以其他全局方式处理错误?你能指点我一些资源吗?我想要实现的是,当从服务层抛出错误时,用户会停留在同一页面上,并且例如在表单顶部显示红色警报消息。我已经在播放文档中看到了全局错误处理,但据我所知,如果您想将用户重定向到错误页面,这很有用,但我想留在表单上,​​只是在顶部向用户显示错误形式
    猜你喜欢
    • 2016-11-28
    • 1970-01-01
    • 2016-05-03
    • 1970-01-01
    • 1970-01-01
    • 2018-11-02
    • 2011-07-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多