是的,Play 中的验证是同步设计的。我认为这是因为假设大多数时候表单验证中没有 I/O:字段值仅检查大小、长度、与正则表达式的匹配等。
验证建立在play.api.data.validation.Constraint
从验证值到存储函数的基础上ValidationResult
(Valid
或者Invalid
,这里没有地方放Future
)。
/**
* A form constraint.
*
* @tparam T type of values handled by this constraint
* @param name the constraint name, to be displayed to final user
* @param args the message arguments, to format the constraint name
* @param f the validation function
*/
case class Constraint[-T](name: Option[String], args: Seq[Any])(f: (T => ValidationResult)) {
/**
* Run the constraint validation.
*
* @param t the value to validate
* @return the validation result
*/
def apply(t: T): ValidationResult = f(t)
}
verifying
只需使用用户定义的函数添加另一个约束。
所以我认为 Play 中的数据绑定并不是为在验证时执行 I/O 而设计的。使其异步会使其更复杂且更难使用,因此它保持简单。让框架中的每一段代码都处理包装在Future
s 中的数据是多余的。
如果您需要使用 ReactiveMongo 进行验证,您可以使用Await.result
. ReactiveMongo 在任何地方都返回 Futures,您可以阻塞直到这些 Futures 完成以在verifying
函数内部获取结果。是的,它会在 MongoDB 查询运行时浪费一个线程。
object Application extends Controller {
def checkUser(e:String, p:String):Boolean = {
// ... construct cursor, etc
val result = cursor.toList().map( _.length != 0)
Await.result(result, 5 seconds)
}
val loginForm = Form(
tuple(
"email" -> email,
"password" -> text
) verifying("Invalid user name or password", fields => fields match {
case (e, p) => checkUser(e, p)
})
)
def index = Action { implicit request =>
if (loginForm.bindFromRequest.hasErrors)
Ok("Invalid user name")
else
Ok("Login ok")
}
}
也许有办法通过使用continuations不浪费线程,而不是尝试过。
我认为在 Play 邮件列表中讨论这个很好,也许很多人想在 Play 数据绑定中做异步 I/O(例如,用于检查数据库的值),所以有人可能会在 Play 的未来版本中实现它。