-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
192 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
sbt.version=1.5.1 | ||
sbt.version=1.8.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
99 changes: 99 additions & 0 deletions
99
src/main/scala-3/com/evolutiongaming/concurrent/FutureHelper.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
package com.evolutiongaming.concurrent | ||
|
||
import scala.collection.{BuildFrom, immutable} | ||
import scala.collection.immutable.Seq | ||
import scala.concurrent.{ExecutionContext, Future, Promise} | ||
import scala.util.control.NonFatal | ||
import scala.util.{Failure, Try} | ||
|
||
object FutureHelper { | ||
private val futureUnit = ().future | ||
private val futureNone = Option.empty.future | ||
private val futureSeq = Seq.empty.future | ||
private val futureNil = Nil.future | ||
private val futureTrue = true.future | ||
private val futureFalse = false.future | ||
|
||
|
||
implicit class FutureObjOps(val self: Future.type) extends AnyVal { | ||
|
||
def unit: Future[Unit] = futureUnit | ||
|
||
def none[T]: Future[Option[T]] = futureNone | ||
|
||
def seq[T]: Future[Seq[T]] = futureSeq | ||
|
||
def nil[T]: Future[List[T]] = futureNil | ||
|
||
def `true`: Future[Boolean] = futureTrue | ||
|
||
def `false`: Future[Boolean] = futureFalse | ||
|
||
@deprecated("use `foldUnit1` instead", "1.0.5") | ||
def foldUnit[T](iter: Iterable[Future[T]]): Future[Unit] = { | ||
self.foldUnit1(iter)(CurrentThreadExecutionContext) | ||
} | ||
|
||
def foldUnit1[T](iter: Iterable[Future[T]])(implicit executor: ExecutionContext): Future[Unit] = { | ||
Future.foldLeft(iter.toList)(()) { (_, _) => () } | ||
} | ||
|
||
def foldLeft[T, S](iter: immutable.Iterable[Future[T]])(s: S)(f: (S, T) => S)(implicit ec: ExecutionContext): Future[S] = { | ||
val iterator = iter.iterator | ||
|
||
def foldLeft(s: S): Future[S] = { | ||
if (iterator.isEmpty) s.future | ||
else iterator.next().flatMap { value => foldLeft(f(s, value)) } | ||
} | ||
|
||
foldLeft(s) | ||
} | ||
|
||
def sequenceSuccessful[A, M[X] <: IterableOnce[X]](in: M[Future[A]])(implicit cbf: BuildFrom[M[Future[A]], A, M[A]], executor: ExecutionContext): Future[M[A]] = { | ||
in.iterator.foldLeft(Future.successful(cbf.newBuilder(in))) { | ||
(acc, f) => acc.flatMap(acc => f.map(acc += _).recover { case _ => acc }) | ||
}.map(_.result())(CurrentThreadExecutionContext) | ||
} | ||
} | ||
|
||
|
||
implicit class FutureOps[T](val self: Future[T]) extends AnyVal { | ||
|
||
@deprecated("use `as` instead", "1.0.4") | ||
def mapVal[TT](value: TT): Future[TT] = as(value) | ||
|
||
def as[TT](value: TT): Future[TT] = self.map(_ => value)(CurrentThreadExecutionContext) | ||
|
||
def unit: Future[Unit] = as(()) | ||
|
||
def flatten[TT](implicit ev: T <:< Future[TT]): Future[TT] = self.flatMap(ev)(CurrentThreadExecutionContext) | ||
|
||
def transform[TT](f: Try[T] => Try[TT])(implicit ec: ExecutionContext): Future[TT] = { | ||
val p = Promise[TT]() | ||
self.onComplete { result => p.complete(try f(result) catch { case NonFatal(t) => Failure(t) }) } | ||
p.future | ||
} | ||
} | ||
|
||
|
||
implicit class AnyFutureOps[T](val self: T) extends AnyVal { | ||
|
||
def future: Future[T] = Future.successful(self) | ||
|
||
def traverseSequentially[A, B, M[X] <: IterableOnce[X]](in: M[A])(f: A => Future[B]) | ||
(implicit buildFrom: BuildFrom[M[A], B, M[B]]): Future[M[B]] = { | ||
|
||
implicit val ec = CurrentThreadExecutionContext | ||
|
||
val builder = buildFrom.newBuilder(in) | ||
builder sizeHint in.iterator.size | ||
|
||
in.iterator.foldLeft(Future successful builder) { (prev, next) => | ||
for { | ||
prev <- prev | ||
next <- f(next) | ||
} yield prev += next | ||
}.map { builder => builder.result() } | ||
} | ||
} | ||
} |
5 changes: 3 additions & 2 deletions
5
src/test/scala-2.12/com/evolutiongaming/concurrent/FutureHelperSpec.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
5 changes: 3 additions & 2 deletions
5
src/test/scala-2.13/com/evolutiongaming/concurrent/FutureHelperSpec.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
70 changes: 70 additions & 0 deletions
70
src/test/scala-3/com/evolutiongaming/concurrent/FutureHelperSpec.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
package com.evolutiongaming.concurrent | ||
|
||
import com.evolutiongaming.concurrent.FutureHelper._ | ||
import org.scalatest.funsuite.AnyFunSuite | ||
import org.scalatest.matchers.should._ | ||
|
||
import scala.concurrent.duration._ | ||
import scala.concurrent.{Await, Future, Promise, TimeoutException} | ||
import scala.util.Success | ||
|
||
class FutureHelperSpec extends AnyFunSuite with Matchers { | ||
import FutureHelperSpec._ | ||
|
||
test("traverseSequentially") { | ||
|
||
val promise = Promise[Int]() | ||
|
||
val futures = List(Future.successful(1), promise.future, Future.successful(3)) | ||
val future = Future.traverseSequentially(futures)(identity) | ||
the[TimeoutException] thrownBy future.await(100.millis) | ||
|
||
promise.success(2) | ||
|
||
Await.result(future, 3.seconds) shouldEqual List(1, 2, 3) | ||
} | ||
|
||
test("sequenceSuccessful") { | ||
implicit val ec = CurrentThreadExecutionContext | ||
val futures = List(Future.successful(1), Future.failed(new RuntimeException()), Future.successful(3)) | ||
Future.sequenceSuccessful(futures).await() shouldEqual List(1, 3) | ||
} | ||
|
||
test("as") { | ||
Future.successful(()).as("").await() shouldEqual "" | ||
} | ||
|
||
test("true") { | ||
Future.`true`.await() shouldEqual true | ||
} | ||
|
||
test("false") { | ||
Future.`false`.await() shouldEqual false | ||
} | ||
|
||
test("nil") { | ||
Future.nil[Int].await() shouldEqual Nil | ||
} | ||
|
||
test("none") { | ||
Future.none[Int].await() shouldEqual None | ||
} | ||
|
||
test("unit") { | ||
Future.unit.value shouldEqual Some(Success(())) | ||
Future.nil.unit.value shouldEqual Some(Success(())) | ||
} | ||
|
||
test("foldLeft") { | ||
val futures = List(Future.successful(1), Future.successful(2)) | ||
val ops = FutureHelper.FutureObjOps(Future) | ||
ops.foldLeft(futures)(List.empty[Int]) { (s, a) => a :: s }(CurrentThreadExecutionContext).await() shouldEqual List(2, 1) | ||
} | ||
} | ||
|
||
object FutureHelperSpec { | ||
|
||
implicit class FutureHelperSpecFutureOps[A](val future: Future[A]) extends AnyVal { | ||
def await(timeout: FiniteDuration = 3.seconds): A = Await.result(future, timeout) | ||
} | ||
} |