Skip to content

Commit a1dd08e

Browse files
authored
Merge pull request #545 from japgolly/sync-promise
Make AsyncCallback.promise synchronous once completed
2 parents 7fb83f4 + 391ffc8 commit a1dd08e

File tree

4 files changed

+63
-4
lines changed

4 files changed

+63
-4
lines changed

core/src/main/scala/japgolly/scalajs/react/AsyncCallback.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package japgolly.scalajs.react
22

3-
import japgolly.scalajs.react.internal.{catchAll, identityFn, newJsPromise}
3+
import japgolly.scalajs.react.internal.{catchAll, identityFn, newJsPromise, SyncPromise}
44
import scala.collection.generic.CanBuildFrom
55
import scala.concurrent.duration.FiniteDuration
66
import scala.concurrent.{ExecutionContext, Future}
@@ -28,8 +28,8 @@ object AsyncCallback {
2828
*/
2929
def promise[A]: CallbackTo[(AsyncCallback[A], Try[A] => Callback)] =
3030
for {
31-
(p, pc) <- newJsPromise[A]
32-
} yield (fromJsPromise(p), pc)
31+
p <- SyncPromise[A]
32+
} yield (AsyncCallback(p.onComplete), p.complete)
3333

3434
def first[A](f: (Try[A] => Callback) => Callback): AsyncCallback[A] =
3535
new AsyncCallback(g => CallbackTo {
@@ -134,7 +134,7 @@ object AsyncCallback {
134134
def fromCallbackToJsPromise[A](c: CallbackTo[js.Promise[A]]): AsyncCallback[A] =
135135
c.asAsyncCallback.flatMap(fromJsPromise(_))
136136

137-
@inline implicit def asynCallbackCovariance[A, B >: A](c: AsyncCallback[A]): AsyncCallback[B] =
137+
@inline implicit def asyncCallbackCovariance[A, B >: A](c: AsyncCallback[A]): AsyncCallback[B] =
138138
c.widen
139139

140140
/** Not literally tail-recursive because AsyncCallback is continuation-based, but this utility in this shape may still
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package japgolly.scalajs.react.internal
2+
3+
import japgolly.scalajs.react.Callback
4+
import scala.util.Try
5+
import japgolly.scalajs.react.CallbackTo
6+
7+
/** Promise that is synchronous once its value has been set. */
8+
final class SyncPromise[A] {
9+
private var result = Option.empty[Try[A]]
10+
private var callbacks = List.empty[Try[A] => Callback]
11+
12+
val complete: Try[A] => Callback =
13+
t =>
14+
Callback {
15+
if (result.isEmpty) {
16+
result = Some(t)
17+
try
18+
callbacks.foreach(_(t).runNow())
19+
finally
20+
callbacks = Nil
21+
}
22+
}
23+
24+
def onComplete: (Try[A] => Callback) => Callback =
25+
f =>
26+
Callback {
27+
result match {
28+
case Some(t) => f(t).runNow()
29+
case None => callbacks :+= f
30+
}
31+
}
32+
}
33+
34+
object SyncPromise {
35+
def apply[A]: CallbackTo[SyncPromise[A]] =
36+
CallbackTo(new SyncPromise)
37+
}

doc/changelog/1.4.2.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* `AsyncCallback#race(…).toCallback` should start a new race each time the `Callback` is invoked
66
* `AsyncCallback#flatten` wouldn't compile (oops)
77
* `AsyncCallback.fromFuture` should be sync if future already complete
8+
* `AsyncCallback.promise` should be sync if promise already complete
89
* Fix bug where in `fullOptJS`, using a key and a ref would result in the key being lost.
910
* Fix CSS-Grid style definitions to not cause React warnings
1011

test/src/test/scala/japgolly/scalajs/react/core/AsyncCallbackTest.scala

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import japgolly.scalajs.react.{AsyncCallback, Callback}
44
import utest._
55
import scala.concurrent.Future
66
import scala.concurrent.ExecutionContext.Implicits.global
7+
import scala.util.Success
78

89
object AsyncCallbackTest extends TestSuite {
910

@@ -49,6 +50,26 @@ object AsyncCallbackTest extends TestSuite {
4950
future.isCompleted ==> true
5051
}
5152
}
53+
54+
'promise {
55+
val (ac, complete) = AsyncCallback.promise[Int].runNow()
56+
var r1,r2,r3 = 0
57+
ac.map(i => r1 = i).toCallback.runNow()
58+
r1 ==> 0
59+
complete(Success(666)).runNow()
60+
r1 ==> 666
61+
r2 ==> 0
62+
r1 = 123
63+
ac.map(i => r2 = i).toCallback.runNow()
64+
r1 ==> 123
65+
r2 ==> 666
66+
r3 ==> 0
67+
r2 = 123
68+
ac.map(i => r3 = i).toCallback.runNow()
69+
r1 ==> 123
70+
r2 ==> 123
71+
r3 ==> 666
72+
}
5273
}
5374

5475
}

0 commit comments

Comments
 (0)