2016-06-08 25 views
0

我有一系列參數。對於每個參數,我必須執行數據庫查詢,這可能會或可能不會返回結果。簡單地說,我需要在第一個結果非空後停止。當然,我想避免不必要的電話。需要注意的是 - 我需要將此操作包含爲另一個未來 - 或任何「最具反應性」的方法。 說起代碼:有條件的期貨鏈條

//that what I have 
def dbQuery(p:Param): Future[Option[Result]] = {} 

//my list of params 
val input = Seq(p1,p2,p3) 

//that what I need to implements 
def getFirstNonEmpty(params:Seq[Param]): Future[Option[Result]] 

我知道我能可能只是包裝全功能的又一Future和執行順序碼(等待BRRR ...?),但不是最乾淨的解決方案。 我可以以某種方式創建期貨的延遲初始化集合,像

params.map (p => FutureWhichWontStartUnlessAskedWhichWrapsOtherFuture { dbQuery(p) }).findFirst(!_.isEmpty()) 

我相信這是可能的!

+1

第一件事來我的腦海:'params.toStream.map {P =>的DBQuery(P)} {.dropWhile F => f.value.isEmpty} .head' –

+0

@VictorMoroz引述'未來.value ScalaDoc:*如果未來未完成,返回值將爲None *。我認爲你不想因爲未完成而跳過未來。它還會返回一個'Result'而不是'Future [Option [Result]]'。 –

+0

參數序列是否有序?換句話說,如果'p1'和'p2'都會產生一個非空結果,那麼使用p1結果而不是'p2'結果很重要嗎?如果不是,您可以考慮從Futures的第一個(即最快)返回非空的集合中啓動所有參數並獲取。 – jwvh

回答

1

你對這樣的事情有什麼看法?

def getFirstNonEmpty(params: Seq[Param]): Future[Option[Result]] = { 
    params.foldLeft(Future.successful(Option.empty[Result])) { (accuFtrOpt, param) => 
    accuFtrOpt.flatMap { 
     case None => dbQuery(param) 
     case result => Future.successful(result) 
    } 
    } 
} 
+0

唯一的問題是它對列表上的每個元素都進行了評估 - 即使第一個dbQuery()返回,我們仍然遍歷其他元素只是爲了產生'Future.successful(result)',解包並重新包裝。 – M4ks

+0

沒錯,迭代到集合的末尾,但如果第一個返回結果,則不會執行更多查詢。 –

+0

我認爲這很簡單,並防止不必要的數據庫命中,如果它通過整個集合運行,它是否重要?如果你沒有做太多或者有很長的params列表,它可能是好的 – Qingwei

0

這可能是矯枉過正,但如果你是開放的使用scalaz我們可以使用OptionTfoldMap做到這一點。

With OptionT我們將FutureOption合併成一個結構。我們可以使用OptionT.orElse獲得兩個Future s中的第一個,結果爲非空。

import scalaz._, Scalaz._ 
import scala.concurrent.Future 
import scala.concurrent.ExecutionContext.Implicits.global 

val someF: Future[Option[Int]] = Future.successful(Some(1)) 
val noneF: Future[Option[Int]] = Future.successful(None) 

val first = OptionT(noneF) orElse OptionT(someF) 
first.run // Future[Option[Int]] = Success(Some(1)) 

我們現在可以用reduce標準庫中得到一個List的第一個非空Future(然而,這將運行所有Future S):

List(noneF, noneF, someF).map(OptionT.apply).reduce(_ orElse _).run 

但是隨着List(或其他收集)我們不能確定至少有一個元素,所以我們需要使用fold並傳遞一個起始值。斯卡拉茲可以通過使用Monoid爲我們做這項工作。我們將使用的Monoid[OptionT[Future, Int]]將提供起始值並將Future與上面使用的orElse結合起來。

type Param = Int 
type Result = Int 
type FutureO[x] = OptionT[Future, x] 

def query(p: Param): Future[Option[Result]] = 
    Future.successful{ println(p); if (p > 2) Some(p) else None } 

def getFirstNonEmpty(params: List[Param]): Future[Option[Result]] = { 
    implicit val monoid = PlusEmpty[FutureO].monoid[Result] 
    params.foldMap(p => OptionT(query(p))).run 
} 

val result = getFirstNonEmpty(List(1,2,3,4)) 
// prints 1, 2, 3 
result.foreach(println) // Some(3) 
+0

List(noneF,noneF,someF).map(OptionT.apply).reduce(_ orElse _)run'的問題是它會一直運行所有的期貨,而不管返回者的價值,只是嘗試使用'List(someF,someF,someF)' – M4ks

+0

嗯,看起來像有類似的問題,像其他答案 - 函數,實際上,遍歷整個輸入,不會評估'p => OptionT(query(p))'超過需要,但仍會創建它。 – M4ks

+0

@ M4ks對於使用'reduce'的對象是正確的,但是'foldMap'不會創建另一個'Future'對嗎? –