Как не выполнить обещание в 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 3

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
}