Как не выполнить обещание в Scala
В документации Scala есть пример того, как выбрать будущее, которое преуспевает быстрее, используя обещания.
Http://docs.scala-lang.org/overviews/core/futures.html#promises
def first[T](f: Future[T], g: Future[T]): Future[T] = {
val p = promise[T]
f onSuccess {
case x => p.trySuccess(x)
}
g onSuccess {
case x => p.trySuccess(x)
}
p.future
}
Эта функция возвращает будущее, которое преуспевает первым, и если одно из них не удается, оно никогда не завершается.
Можно ли изменить это таким образом, что даже если другое будущее терпит неудачу, то второе возвращается, если оно успешно, и если оба из них успешны, то более быстрый выбирается так же, как и код сейчас.
4 ответа:
Вы можете добавить следующее:
f onFailure { case e => g onFailure { case _ => p.failure(e) } }
Когда оба фьючерса провалены, это будет провалить обещание с тем же исключением, что и
f
. Вы можете уточнить это, чтобы создать исключение, которое записывает 2 исключения, приходящие изf
иg
, Если это необходимо.
Я рекомендую вам следовать совету Элвина Александера для фьючерсов и обещаний в Scala здесь
Я считаю, что это лучший способ работать с фьючерсами
package futures import scala.concurrent.{Future => ConcurrentTask} // rename import scala.concurrent.ExecutionContext.Implicits.global import scala.util.{Failure, Success} import Utils.sleep object FutureAsConcurrentTask extends App { // run some long-running task (task has type Future[Int] in this example) val task = ConcurrentTask { Cloud.executeLongRunningTask } // whenever the task completes, execute this code task.onComplete { case Success(value) => println(s"Got the callback, value = $value") case Failure(e) => println(s"D'oh! The task failed: ${e.getMessage}") } // do your other work println("A ..."); sleep(100) println("B ..."); sleep(100) println("C ..."); sleep(100) println("D ..."); sleep(100) println("E ..."); sleep(100) println("F ..."); sleep(100) }
Вот базовый шаблон для выбора самого быстрого будущего или тайм-аута, если все они слишком медленные:
import scala.concurrent._ import scala.concurrent.duration._ import scala.concurrent.ExecutionContext.Implicits.global import scala.util.{ Success, Failure } import akka.actor._ import akka.pattern.after object GetFastestFutureOrTimeout extends App { val e = new TimeoutException("TimeoutException") val system = ActorSystem("GetFastestFutureOrTimeout") val f1 = Future { Thread.sleep(200); "this is f1" } val f2 = Future { Thread.sleep(100); "this is f2" } val timeoutFuture = after(500.milliseconds, using = system.scheduler) { Future.failed(e) } val f = Future.firstCompletedOf(f1 :: f2 :: timeoutFuture :: Nil) f onComplete { case Success(msg) => println(msg) case Failure(err) => println("An error occured: " + err.getMessage) } }
Здесь выводится "это f2". Если бы таймаут timeoutFuture был изменен на 50, он бы напечатал "произошла ошибка: TimeoutException".
Под капотом firstCompletedOf использует обещание вернуть значение первого будущего, которое завершено, см. https://github.com/scala/scala/blob/v2.11.6/src/library/scala/concurrent/Future.scala#L503.
Это одна из основных реализаций, чтобы получить самый быстрый успешный ответ или потерпеть неудачу, если они все потерпели неудачу:
def getFirstSuccessfulResultOrFail[T](requests: List[Future[T]]): Future[T] = { val p = Promise[T]() val countDownLatch = AtomicInt(0) requests.foreach { f => f.onComplete { case Failure(e) => if (countDownLatch.addAndGet(1) == requests.size) p.tryFailure(e) case Success(s) => p.trySuccess(s) } } p.future }