簡體   English   中英

Scala:通過異常處理組合期貨結果

[英]Scala: compose results of futures with exception handling

我是Scala的Future的新手,我還沒有找到解決問題的方法。 我正在嘗試實現以下目標(總體描述:嘗試獲取酒店列表的來賓列表,分別查詢每個酒店):

  1. 對另一個API進行n次調用,每次調用都超時
  2. 合並所有結果(將列表列表轉換為包含所有元素的列表)
  3. 如果單個呼叫失敗,請記錄錯誤並返回一個空列表(基本上,在這種情況下,如果我得到部分結果而不是根本沒有結果會更好)
  4. 理想情況下,如果單個呼叫失敗,請在等待一段時間后重試x次,最終失敗並像沒有重試一樣處理錯誤

這是我的代碼。 HotelReservation代表我將調用的外部API。

import com.typesafe.scalalogging.slf4j.Logging

import scala.concurrent._, ExecutionContext.Implicits.global
import scala.util.{Failure, Success}

case class Guest(name: String, country: String)

trait HotelReservation extends Logging {

  def getGuests(id: Int): Future[List[Guest]] = Future {
    logger.debug(s"getting guests for id $id")
    id match {
      case 1 => List(new Guest("John", "canada"))
      case 2 => List(new Guest("Harry", "uk"), new Guest("Peter", "canada"))
      case 3 => {
        Thread.sleep(4000)
        List(new Guest("Harry", "austriala"))
      }
      case _ => throw new IllegalArgumentException("unknown hotel id")
    }
  }
}

object HotelReservationImpl extends HotelReservation

HotelSystem撥打電話。

import com.typesafe.scalalogging.slf4j.Logging

import scala.util.control.NonFatal
import scala.util.{Failure, Success}
import scala.concurrent._, duration._, ExecutionContext.Implicits.global

class HotelSystem(hres: HotelReservation) {

  def pollGuests(hotelIds: List[Int]): Future[List[Guest]] = {

    Future.sequence(
  hotelIds.map { id => future {
    try {
      Await.result(hres.getGuests(id), 3 seconds)
    } catch {
      case _: Exception =>
        Console.println(s"failed for id $id")
        List.empty[Guest]
    }

  }
  }
).map(_.fold(List())(_ ++ _)) /*recover { case NonFatal(e) =>
  Console.println(s"failed:", e)
  List.empty[Guest]
}*/
  }
}

和測試。

object HotelSystemTest extends App {

  Console.println("*** hotel test start ***")

  val hres = HotelReservationImpl

  val hotel = new HotelSystem(hres)
  val result = hotel.pollGuests(List(1, 2, 3, 6))

  result onSuccess {
    case r => Console.println(s"success: $r")
  }

  val timeout = 5000
  Console.println(s"waiting for $timeout ms")
  Thread.sleep(timeout)
  Console.println("*** test end ***")
}

1和2正在工作。 3也是如此,但我想我在SO的某個地方讀到,嘗試抓住對未來的呼喚不是一個好主意,最好使用recovery。 但是,在這種情況下,如果我使用恢復,則如果有單個失敗,則整個調用都會失敗並返回一個空列表。 關於如何改善這一點的任何想法?

實際上,您可以做兩件不同的事情:忽略嘗試,不要對期貨使用Await。

這是實現pollGuests的更好方法:

Future.sequence(
   hotelIds.map { hotelId =>
      hres.getGuests(hotelId).recover {
         case e: Exception => List.empty[Guest]
      }
   }
).map(_.flatten)

這里的第一點是,由於getGuests()已經為您提供了Future,因此您不必在pollGuests()使用Futures。 您只需使用recover()創建一個新的Future,以便在返回Future時可能的故障已得到處理。

第二點是您不應該使用Await。 它使您的代碼處於阻塞狀態,直到Future准備就緒為止,例如可能凍結整個UI線程。 我假設您正在使用Await來使用try-catch,但是由於使用了restore recover()您不再需要它了。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM