Suppose that we are at a food court and place an order to eat something at a kiosk, we are given a token and a bill. Later, when the order is ready, the kiosk flashes the token number, and upon noticing it, we collect the order.
This is similar to a request with an asynchronous response cycle, where the kiosk acts like the server, the order acts similar to a request, and the token as a promise, which gets resolved when the order is ready.
Most operations are better handled asynchronously. This is also mostly preferred since it does not block server resources until the operation is completed.
Play Action is a helper object, which extends the ActionBuilder
trait. The apply method of the ActionBuilder
trait implements the Action
trait, which we saw earlier. Let's take a look at the relevant code from the ActionBuilder
trait:
trait ActionBuilder[+R[_]] extends ActionFunction[Request, R] { self => final def apply[A](bodyParser: BodyParser[A])(block: R[A] => Result): Action[A] = async(bodyParser) { req: R[A] => Future.successful(block(req)) } final def async[A](bodyParser: BodyParser[A])(block: R[A] => Future[Result]): Action[A] = composeAction(new Action[A] { def parser = composeParser(bodyParser) def apply(request: Request[A]) = try { invokeBlock(request, block) } catch { // NotImplementedError is not caught by NonFatal, wrap it case e: NotImplementedError => throw new RuntimeException(e) // LinkageError is similarly harmless in Play Framework, since automatic reloading could easily trigger it case e: LinkageError => throw new RuntimeException(e) } override def executionContext = ActionBuilder.this.executionContext }) ... }
Notice that the apply
method itself calls the async
method internally. The async
method expects us to define the Action, which results in Future[Result]
, thereby aiding us to write non-blocking code.
We will use the same method to define an asynchronous Action. Assume that we need to fetch the requested file from a remote client, consolidate/analyze the data, and then send the results. Since we do not know the size of the file and the status of network connectivity with a remote client, it is better to handle the Action asynchronously. The action will be defined in this way:
def getReport(fileName:String ) = Action.async { Future { val file:File = new File(fileName) if (file.exists()) { val info = file.lastModified() Ok(s"lastModified on ${new Date(info)}") } else NoContent } }
After fetching the file, if it is empty, we send a response with a status code of 204, else we continue with the processing and send the processed data as a part of the result.
We may come across an instance, as we saw in the previous example, get report, that we do not wish to wait longer than 10 seconds for the remote client to fetch the file. In this case, we'll need to modify the Action definition in this way:
def getReport(fileName: String) = Action.async { val mayBeFile = Future { new File(fileName) } val timeout = play.api.libs.concurrent.Promise.timeout("Past max time", 10, TimeUnit.SECONDS) Future.firstCompletedOf(Seq(mayBeFile, timeout)).map { case f: File => if (f.exists()) { val info = f.lastModified() Ok(s"lastModified on ${new Date(info)}") } else NoContent case t: String => InternalServerError(t) } }
So, if the remote client doesn't respond with the requested file in 10 seconds, we will get a response with status code 500 and the content as the message we set for the timeout, Past max time
.
3.142.12.207