Bring back IO#runAsync and friends?
See original GitHub issueNice to meet you!
I’m having a difficulty using Cats Effect 3 on Scala.js due to the change of the thread model.
Some JS functions such as Event.preventDefault()
have to be run synchronously or won’t have any effect. In CE2, we can just use IO
because it is run synchronously until a first async boundary. For example, if we wanted to implement some click
event handler:
val more: IO[Unit] = ???
// OK in CE2, bad in CE3
def onClick(e: org.scalajs.dom.Event): IO[Unit] =
IO{ e.preventDefault() } *> more
In CE3, we must use SyncIO
without converting it into an IO
. Unless I’m missing something, we have to resort to IO#unsafeRunAndForget
:
val more: IO[Unit] = ???
// bad
def onClick(e: org.scalajs.dom.Event): SyncIO[Unit] =
SyncIO{ e.preventDefault() }.to[IO] *> more
// OK
def onClick(e: org.scalajs.dom.Event): SyncIO[Unit] =
SyncIO{ e.preventDefault(); more.unsafeRunAndForget() }
// OK
def onClick(e: org.scalajs.dom.Event): SyncIO[Unit] =
SyncIO{ e.preventDefault() } *> SyncIO{ more.unsafeRunAndForget() }
The story is complicated because often more
in the above example must be asynchronous. For example, it can be an AJAX call triggered by clicking a link. I guess this is going to be a common pattern in my project, and I definitely wouldn’t like to sprinkle unsafeRunAndForget
all over it.
If there were IO#runAsync
like in CE2, the last code could be rewritten like this:
def onClick(e: org.scalajs.dom.Event): SyncIO[Unit] =
SyncIO{ e.preventDefault() } *> more.runAsync(_ => IO.unit)
(I’d like to point out IO#runAsync
would be equivalent to runAff_
in purescript-aff
, and IO#runAndForget
would correspond to launchAff_
.)
The migration guide refers to Dispatcher
as the replacement of IO#runAsync
but it doesn’t have any methods that return SyncIO[_]
. I think it is reasonable because Dispatcher
shouldn’t be coupled to SyncIO
, but it may be helpful to have IO
, as a concrete effect type, have a conversion from IO
to SyncIO
.
Issue Analytics
- State:
- Created 2 years ago
- Comments:39 (28 by maintainers)
I’m not against a convenience method for this. I much prefer it over changing the execution model of
IO
.So, here is the code that “compiles” (in fs2 jargon) a
Stream.eval
, which is represented as an object of theEval
subclass ofPull
, into the target effect typeF
: https://github.com/typelevel/fs2/blob/main/core/shared/src/main/scala/fs2/Pull.scala#L1027-L1036. It makes a call to theScope.interruptibleEval
method, which then refers to theInterruptibleContext.eval
function. It is in the code of that lasteval
that the race you are referring to occurs.