是的,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 而设计的。使其异步会使其更复杂且更难使用,因此它保持简单。让框架中的每一段代码都可以处理 Futures 中包装的数据是多余的。
如果你需要对 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 的未来版本中实现它.