Update and get
olynch opened this issue · 5 comments
I want there to be a method on a Var[A]
that takes in a function A -> (A,B)
and returns a B
. This would be a generalization of update
. This can be simulated using now()
and set()
, but then there are two separate transactions.
Also, is there a way of doing this without modifying Airstream itself that I am missing?
I don't think now()
involves a transaction 🤔
Sorry for taking a while to get back to this; I'm just now opening up my laminar project again. The problem is that I'm worried that that the following code is incorrect:
val a = $a.now()
val (newA,b) = f(a)
$a.set(newA)
I.e., I'm worried that in between getting the value of $a
and setting it, something else could write to $a
and then that write would be forgotten. But I guess because JavaScript is single-threaded that's actually not a problem?
@olynch Although Javascript itself is single threaded, because of Airstream's transaction system, the $a.set
method is not guaranteed to actually update the Var immediately, it can de delayed until the current transaction is done. See https://github.com/raquo/Airstream#var-transaction-delay for details.
So yes, if you just write that snippet of yours, your other code can indeed change the value of $a
after you called $a.now()
, but before you called $a.set
, for example if you have the following all inside the same Observer callback:
$a.set(ignoredA) // this does not update `$a` immediately because we're inside of an observer
val a = $a.now() // this reads the value immediately, so it reads the original value from `$a`, not `ignoredA` which wasn't set yet
val (newA,b) = f(a)
$a.set(newA) // this is also run at a delay, but it will run after the first `$a.set` above, overriding `ignoredA`.
As I mentioned, to solve this, you need to wrap the code that you want to run together/unbroken in a new Transaction. There are several ways to do it, but I guess the most obvious is this:
$a.set(firstA) // put this here, so that its transaction executes before the following `new Transaction`
new Transaction { _ =>
val a = $a.now() // will read `firstA`, because this `new transaction` will be run after the `$a.set(firstA)` transaction.
val (newA,b) = f(a)
$a.set(newA)
}
For this to work, any $a.set
/ $.update
that you do before $a.now()
must be outside of new Transaction
, if you want $a.now()
to see those updates.
Notice that you can't return B
from the new Transaction
block, for similar reasons why you can't return B
from an expression that is Future[B]
– the B
is not available yet.
We could potentially make Transactions return a value and provide a Future-like API with onComplete
to get the value when it's ready, but there would need to be some real strong justification for that.
OK, this is actually fine though, because I could actually wrap this in a future and then pass b
into the callback. This is already inside of some async stuff, so this meshes well. I think you can close this issue now. Thank you so much for helping me out here!