0

我在 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
4

1 回答 1

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 =>
  ...
}

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

细节

如果您查看(参见此处recover)方法的文档,您会发现它需要一个.Futurepf: PartialFunction[Throwable, U]

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

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

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

先进的

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

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

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

于 2017-03-28T22:22:27.793 回答