在我的播放控制器操作方法中处理我的未来 returns 和 None 的情况,

Handling a case of when my future returns a None in my play controller action method,

我想通过下面的更新操作进行重构,使其看起来更具可读性,同时更好地处理失败案例

userService有以下功能:

class UserService {
    def getUserByUsername: Future[Option[Int]] // which is the UserId
    def getUserById: Future[User]
}

我的动作是这样的:

def update(userId: Int) = Action.async { implicit request =>
    request.body.validate[User] match {
        case JsSuccess(user, _) => {
            userService.getUserByUsername(user.username).map { userId => 
                userService.getUserById(userId.get).map { existingUser =>
                    userService.update(user.username)

                    Ok
                }
            }
        }
        case JsError(err) => Future.sucessful(BadRequest(err))
    }
}
  1. 如何处理 getUserByUsername returns a None?
  2. 如果是 for comprehension 的话会不会看起来更干净一些,是不是更好的风格?

您的问题中缺少一些数据,例如 User 模型的 case classes,userService class。 最好附上原函数

无论如何,我会做如下事情:

 def update(userId: Int) = Action { implicit request =>
    request.body.validate[User] match {
      case JsSuccess(user: User, _) => {
        val userId = getUserByUsername(user.username)
        userId match {
          case Some(userId) => {
            for {
              _ <- userService.getUserById(userId)
              _ <- userService.update(user.username)
            } yield Ok
          }.recover {
            case t: Throwable => 
              Metrics.errOnUpdate.increment() // Some metric to monitor 
              logger.error(s"update userId: $userId failed with ex: ${t.getMessage}") // log the error 
              InternalServerError(Json.toJson(Json.obj("error" -> "Failure occured on update"))) // return custom made exception to the client
          }
          case None => Future.successful(NotFound(s"No such user with ${user.username}"))
        }
      }
      case JsError(err) => Future.sucessful(BadRequest(err))
    }
  }

注意:如果.updatereturns未来,你实际上没有等到update才返回Ok给用户,因此,如果它失败了,它仍然是 returns Ok。 要解决这个问题,请使用 flatMap 然后映射 update response.

的值

如果您愿意,也可以将 getUserByIdupdate 的恢复分开。


编辑:

  def update(userId: Int) = Action { implicit request =>
    request.body.validate[User] match {
      case JsSuccess(user: User, _) => {
          getUserByUsername(user.username).flatMap {
             case Some(userId) => for {
                _ <- userService.getUserById(userId)
                _ <- userService.update(user.username)
              } yield Ok
             case None => Future.successful(NotFound(s"No such user with ${user.username}"))
          }
        }.recover {
          case t: Throwable =>
            Metrics.errOnUpdate.increment() // Some metric to monitor 
            logger.error(s"update userId: $userId failed with ex: ${t.getMessage}") // log the error 
            InternalServerError(Json.toJson(Json.obj("error" -> "Failure occured on update"))) // return custom made exception to the client
        }
      }
      case JsError(err) => Future.sucessful(BadRequest(err))
    }
  }

首先,您可能需要使用 Option.fold:

@inline final def fold[B](ifEmpty: => B)(f: A => B)

那么你可以这样做:

def update(userId: Int) = Action.async { implicit request =>
    def handleJsonErrors(errors: Seq[(JsPath, collection.Seq[JsonValidationError])]): Future[Result] = ???
    def updateUser(userWithoutId: User): Future[Result] = {
        for {
            userId <- userService.getUserByUsername(userWithoutId.username)
            _ <- userService.getUserById(userId.get)
            _ <- userService.update(userWithoutId.username)
        } yield {
            Ok
        }
    }

    request.body.asJson.fold {
        Future.successful(BadRequest("Bad json"))
    } {
        _.validate[User].fold(handleJsonErrors, updateUser).recover {
            case NonFatal(ex) =>
                InternalServerError
        }
    }
}