alifirat
alifirat

Reputation: 2927

Wait future completion to execute a another one for a sequence

I met a read / write problem this last days and I cannot fix an issue in my test. I have a JSON document based on the following model

package models 

import play.api.libs.json._

object Models {

  case class Record
  (
    id : Int, 
    samples : List[Double]
  )

  object Record {
    implicit val recordFormat = Json.format[Record]
  }
}

I have two functions : one to read a record and an another one to update.

case class MongoIO(futureCollection : Future[JSONCollection]) {

  def readRecord(id : Int) : Future[Option[Record]] = 
    futureCollection
      .flatMap { collection =>
      collection.find(Json.obj("id" -> id)).one[Record]
    }

  def updateRecord(id : Int, newSample : Double) : Future[UpdateWriteResult]= {
    readRecord(id) flatMap { recordOpt =>
      recordOpt match {
        case None =>
          Future { UpdateWriteResult(ok = false, -1, -1, Seq(), Seq(), None, None, None) }
        case Some(record) =>
          val newRecord = 
            record.copy(samples = record.samples :+ newSample)
          futureCollection
          .flatMap { collection =>
            collection.update(Json.obj("id" -> id), newRecord)
          }
      }
    }
  }
}

Now, I have a List[Future[UpdateWriteResult]] corresponds to a many updates on the document but what I want is that : wait the future is complete to execute the second one then wait the completion of the second to execute the third. I tried to do that with a foldLeft and flatMap like this :

val l : List[Future[UpdateWriteResult]] = ...
println(l.size) // give me 10
l
.foldLeft(Future.successful(UpdateWriteResult(ok = false, -1, -1, Seq(), Seq(), None, None, None))) { 
     case (cur, next) => cur.flatMap(_ => next)
  }

but the document is never updated like excepted : instead to have a document with a samples list of size 10, I got a list of 1 samples. So the read is faster than the write (impression that I have) and also using a combinaison of foldLeft / flatMap seems to do not wait the completion of the current future so how can I fix this issue properly (without Await) ?

Update

val futureCollection = DB.getCollection("foo")
val mongoIO = MongoIO(futureCollection)
val id = 1
val samples = List(1.1, 2.2, 3.3)
val l : List[Future[UpdateWriteResult]] = samples.map(sample => mongoIO.updateRecord(id, sample))

Upvotes: 0

Views: 353

Answers (2)

Viktor Klang
Viktor Klang

Reputation: 26579

I took the liberty of making some minor modification to you original sources:

case class MongoIO(futureCollection : Future[JSONCollection]) {

  def readRecord(id : Int) : Future[Option[Record]] = 
    futureCollection.flatMap(_.find(Json.obj("id" -> id)).one[Record])

  def updateRecord(id : Int, newSample : Double) : Future[UpdateWriteResult] =
    readRecord(id) flatMap {
      case None => Future.successful(UpdateWriteResult(ok = false, -1, -1, Nil, Nil, None, None, None))
      case Some(record) =>
        val newRecord = record.copy(samples = record.samples :+ newSample)
        futureCollection.flatMap(_.update(Json.obj("id" -> id), newRecord))
    }
}

Then we only need to write a serialized/sequential update:

def update(samples: Seq[Double], id: Int): Future[Unit] = s match {
  case sample +: remainingSamples => updateRecord(id, sample).flatMap(_ => update(remainingSamples, id))
  case _ => Future.successful(())
}

Upvotes: 1

Alvaro Carrasco
Alvaro Carrasco

Reputation: 6172

You have to do the foldLeft on the samples:

(mongoIO.updateRecord(id, samples.head) /: samples.tail) {(acc, next) =>
  acc.flatMap(_ => mongoIO.updateRecord(id, next))
}

updateRecord is what triggers the future, so you have to make sure not to call it until the previous one finishes.

Upvotes: 2

Related Questions