恢复未来时播放控制器操作中的类型不匹配

Type mismatch in Play controller action when recovering a future

我在 return scala play controller 方法中的正确类型时遇到问题 有人可以在这里给我提示吗?我正在使用 for comprehantion 来处理 returns 一个 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

简答

您需要部分功能来恢复未来的故障:

    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 => Option[Result] 类型的函数,然后你可以使用 Function#unlift 将其转换为所需的偏函数。 您也可以直接继承 PartialFunction and implement its two methods (apply and isDefinedAt).