Reputation: 1071
I have the following code that sends back a response to the sender Actor (the responding actor loads a list from a table using Slick):
class ManageUsersData extends Actor {
def receive = {
case _ => {
sender ! loadUsers
}
}
def loadUsers = {
var list = new ListBuffer[String]()
val db = Database.forConfig("dbconfig")
try {
val users: TableQuery[Users] = TableQuery[Users]
val future = db.run(users.result)
future onComplete {
case Success(u) => u.foreach {
user => {
list += user.firstName
}
list
}
case Failure(t) => println("An error has occured: " + t.getMessage)
}
} finally db.close
list
}
}
The problem here is that loadUsers
returns before waiting for the Future to complete. How can this be approached?
Upvotes: 1
Views: 803
Reputation: 7373
As I see it, the easiest approach would be to simply send the future
back to the sender
, instead of the async-filled list
.
As in
def loadUsers = {
val db = Database.forConfig("dbconfig")
try {
val users: TableQuery[Users] = TableQuery[Users]
val future = db.run(users.result)
future.map { //the future
_.map { //the returning Seq
_.firstName
}
}
} finally db.close
}
Now the caller actor has the burden to handle the future or the failure.
This also has the drawback that, if the sender
used the ask
/?
operation, the async result will be a Future
wrapping a further Future
.
You can overcome this issue by using the pipeTo
method, which sends a future message to the caller without needing to bother about unwrapping it.
The downside of piping the result is that the sender
should have a way to identify which reply belongs to which request. A possible solution is to send a request identifier that will be sent back with the answer, so the requesting actor can easily link the twos.
Why would you map on the firstName
attribute in the future result instead of using a projection within the slick query? I assume it's for the sake of keeping the example simple.
Upvotes: 2
Reputation: 12563
The conceptual solution is already mentioned by Jean Logeart. Regarding loadUsers, I think this is a shorter version?
def loadUsers = {
val db = Database.forConfig("dbconfig")
try {
val users: TableQuery[Users] = TableQuery[Users]
db.run(users.result).map(_.firstName)
} catch {
case e: Exception => println("An error has occured: " + e.getMessage)
} finally db.close
}
Upvotes: 3
Reputation: 53809
You should use the pipe pattern:
import akka.pattern.pipe
// ...
def receive = {
val originalSender = sender
loadUsers pipeTo originalSender
}
Upvotes: 3