All Downloads are FREE. Search and download functionalities are using the official Maven repository.

monix.execution.FutureUtils.scala Maven / Gradle / Ivy

/*
 * Copyright (c) 2014-2016 by its authors. Some rights reserved.
 * See the project homepage at: https://monix.io
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package monix.execution

import java.util.concurrent.TimeoutException

import scala.concurrent.duration._
import scala.concurrent.{ExecutionContext, Future, Promise}
import scala.util.control.NonFatal
import scala.util.{Failure, Success, Try}

/** Utilities for Scala's standard `concurrent.Future`. */
object FutureUtils {
  /** Utility that returns a new Future that either completes with
    * the original Future's result or with a TimeoutException in case
    * the maximum wait time was exceeded.
    *
    * @param atMost specifies the maximum wait time until the future is
    *               terminated with a TimeoutException
    * @param s is the Scheduler, needed for completing our internal promise
    * @return a new future that will either complete with the result of our
    *         source or fail in case the timeout is reached.
    */
  def timeout[T](source: Future[T], atMost: FiniteDuration)(implicit s: Scheduler): Future[T] = {
    val err = new TimeoutException
    val promise = Promise[T]()
    val task = s.scheduleOnce(atMost.length, atMost.unit,
      new Runnable { def run() = promise.tryFailure(err) })

    source.onComplete { case r =>
      // canceling task to prevent waisted CPU resources and memory leaks
      // if the task has been executed already, this has no effect
      task.cancel()
      promise.tryComplete(r)
    }

    promise.future
  }

  /** Utility that returns a new Future that either completes with
    * the original Future's result or after the timeout specified by
    * `atMost` it tries to complete with the given `fallback`.
    * Whatever `Future` finishes first after the timeout, will win.
    *
    * @param atMost specifies the maximum wait time until the future is
    *               terminated with a TimeoutException
    * @param fallback the fallback future that gets triggered after timeout
    * @param s is the Scheduler, needed for completing our internal promise
    * @return a new future that will either complete with the result of our
    *         source or with the fallback in case the timeout is reached
    */
  def timeoutTo[T](source: Future[T], atMost: FiniteDuration, fallback: => Future[T])
    (implicit s: Scheduler): Future[T] = {

    val promise = Promise[T]()
    val task = s.scheduleOnce(atMost.length, atMost.unit,
      new Runnable { def run() = promise.tryCompleteWith(fallback) })

    source.onComplete { case r =>
      // canceling task to prevent waisted CPU resources and memory leaks
      // if the task has been executed already, this has no effect
      task.cancel()
      promise.tryComplete(r)
    }

    promise.future
  }

  /** Utility that lifts a `Future[T]` into a `Future[Try[T]]`, exposing
    * error explicitly.
    */
  def materialize[T](source: Future[T])(implicit ec: ExecutionContext): Future[Try[T]] = {
    if (source.isCompleted) {
      Future.successful(source.value.get)
    }
    else {
      val p = Promise[Try[T]]()
      source.onComplete { case result => p.success(result) }
      p.future
    }
  }

  /** Given a mapping functions that operates on successful results as well as
    * errors, transforms the source by applying it.
    *
    * Similar to `Future.transform` from Scala 2.12.
    */
  def transform[T,S](source: Future[T], f: Try[T] => Try[S])(implicit ec: ExecutionContext): Future[S] = {
    val p = Promise[S]()
    source.onComplete(r => try p.complete(f(r)) catch {
      case NonFatal(ex) =>
        if (!p.tryFailure(ex)) ec.reportFailure(ex)
    })
    p.future
  }

  /** Given a mapping functions that operates on successful results
    * as well as errors, transforms the source by applying it.
    *
    * Similar to `Future.transformWith` from Scala 2.12.
    */
  def transformWith[T,S](source: Future[T], f: Try[T] => Future[S])(implicit ec: ExecutionContext): Future[S] = {
    val p = Promise[S]()
    source.onComplete(r => try p.completeWith(f(r)) catch {
      case NonFatal(ex) =>
        if (!p.tryFailure(ex)) ec.reportFailure(ex)
    })
    p.future
  }

  /** Utility that transforms a `Future[Try[T]]` into a `Future[T]`,
    * hiding errors, being the opposite of [[materialize]].
    */
  def dematerialize[T](source: Future[Try[T]])(implicit ec: ExecutionContext): Future[T] = {
    if (source.isCompleted)
      source.value.get match {
        case Failure(error) => Future.failed(error)
        case Success(value) => value match {
          case Success(success) => Future.successful(success)
          case Failure(error) => Future.failed(error)
        }
      }
    else {
      val p = Promise[T]()
      source.onComplete {
        case Failure(error) => p.failure(error)
        case Success(result) => p.complete(result)
      }
      p.future
    }
  }

  /** Creates a future that completes with the specified `result`, but only
    * after the specified `delay`.
    */
  def delayedResult[T](delay: FiniteDuration)(result: => T)(implicit s: Scheduler): Future[T] = {
    val p = Promise[T]()
    s.scheduleOnce(delay.length, delay.unit,
      new Runnable { def run() = p.complete(Try(result)) })
    p.future
  }

  /** Provides extension methods for `Future`. */
  object extensions {
    /** Provides utility methods added on Scala's `concurrent.Future` */
    implicit class FutureExtensions[T](val source: Future[T]) extends AnyVal {
      /** [[FutureUtils.timeout]] exposed as an extension method. */
      def timeout(atMost: FiniteDuration)(implicit s: Scheduler): Future[T] =
        FutureUtils.timeout(source, atMost)

      /** [[FutureUtils.timeoutTo]] exposed as an extension method. */
      def timeoutTo[U >: T](atMost: FiniteDuration, fallback: => Future[U])
        (implicit s: Scheduler): Future[U] =
        FutureUtils.timeoutTo(source, atMost, fallback)

      /** [[FutureUtils.materialize]] exposed as an extension method. */
      def materialize(implicit ec: ExecutionContext): Future[Try[T]] =
        FutureUtils.materialize(source)

      /** [[FutureUtils.dematerialize]] exposed as an extension method. */
      def dematerialize[U](implicit ev: T <:< Try[U], ec: ExecutionContext): Future[U] =
        FutureUtils.dematerialize(source.asInstanceOf[Future[Try[U]]])

      /** [[FutureUtils.transform]] exposed as an extension method. */
      def transform[S](f: Try[T] => Try[S])(implicit ec: ExecutionContext): Future[S] =
        FutureUtils.transform(source, f)

      /** [[FutureUtils.transformWith]] exposed as an extension method. */
      def transformWith[S](f: Try[T] => Future[S])(implicit ec: ExecutionContext): Future[S] =
        FutureUtils.transformWith(source, f)
    }

    /** Provides utility methods for Scala's `concurrent.Future` companion object. */
    implicit class FutureCompanionExtensions(val f: Future.type) extends AnyVal {
      /** [[FutureUtils.delayedResult]] exposed as an extension method. */
      def delayedResult[T](delay: FiniteDuration)(result: => T)(implicit s: Scheduler): Future[T] =
        FutureUtils.delayedResult(delay)(result)
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy