Replies: 3 comments 4 replies
-
Hi @jchitel, thanks for starting this discussion. The topic has come up once or twice before. I don't think the behavior of return .run { send in
for await _ in self.clock.timer(interval: .seconds(1)) {
await send(.timerTicked)
}
} It'd be a little strange if the timer was essentially paused just because But, having said that, I do think there are probably some really interesting and powerful use cases for tying the suspension of I think we can even borrow the syntax from await viewStore.send(action).finish() So, maybe we could have return .run { send in
// Suspend until the effects from .started fully complete.
await send(
.authorizationResponse(self.speechClient.requestAuthorization())
)
.finish()
// ...
} Heck... it might even let you cancel the actions later too 🤔: return .run { send in
// Suspend until the effects from .started fully complete.
let task = await send(
.authorizationResponse(self.speechClient.requestAuthorization())
)
// Cancel any effects from the auth response if they haven't finished in 2 seconds:
try await self.clock.sleep(for: .seconds(2))
task.cancel()
} Lots to think about! We don't personally have time to explore this right now, but if you want to start a branch with some explorations we'd love to take a look. And especially so if you have some interesting use cases for the |
Beta Was this translation helpful? Give feedback.
-
I'm playing around with this atm and the implementation is mostly simple except for one fundamental issue: Effects don't have to emit actions, and they can be observed by things that aren't stores. Consider the following example: enum Action { case doSomething, doOtherThing }
let effect = EffectTask<Action>.run { send in
await send(.doSomething).finish()
await send(.doOtherThing)
}
effect.sink { nextAction in print(nextAction) } In the above case there's no semantic meaning to waiting for doSomething to "finish" because there's no With reference to the TCA codebase, the exact issue is that we can't get any info back from the subscriber here: whereas we have a lot more information in the Store case here The only solutions I can think of rn are 1) removing Publisher support entirely, which would break tons of stuff, or 2) performing some sort of reference type spooky action at a distance such that if the action does eventually trickle up to a Store, it can populate a completion field to communicate back. Neither seems ideal; wondering if anyone else has better ideas. |
Beta Was this translation helpful? Give feedback.
-
This could work if you really need the wait for the prior effect to finish before doing something else functionality:
|
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
There is something unexpected that I recently discovered about the
EffectTask.run { send in ... }
API.I originally noticed that
send()
can beawait
ed, which excited me because I thought that meant that effects can wait for those actions to complete. However, when I read through the source code, I discovered that theSend
type is onlyawait
able because it has@MainActor
. The actualsend
function that theStore
provides to effects is synchronous, and internally it holds onto a reference to any task returned from the child effect, but doesn'tawait
the task.After further review of the source code (btw, I think the source code needs slightly more comments to explain some of the less obvious aspects of the implementation), I can't actually think of any logical reason why the
send
function can'tawait
the task.I'll try to illustrate my point with an example:
On sending
.first
, I would want the order of prints to go like this:Based on my understanding of the source code, however, I believe it would go like this:
Basically, if an effect sends an action that returns another effect, there is no way for the parent effect to wait on the child effect, but I don't see a limitation necessarily preventing this. Since effects are executed in a task within the main actor, they won't start executing until after the synchronous parts of
store.send()
are done. Thus, any child effects will always be new "top-level" actions, so there won't be any logical difference between the first effect and the second effect from the perspective ofstore.send()
.The way that I see it, the following code changes would have to be made:
.run
effect handler instore.send()
Returned task at the bottom of
store.send()
This latter change is probably insufficient because it doesn't properly handle awaiting all child tasks, but there are certainly ways to handle that. Perhaps instead of returning
Void
, each task could return a[Task]
with all child tasks. You'd need to introduce another type because the types of those tasks would be recursive. The aggregate task would create a task group where each task within the group waits for an effect task, then adds all returned tasks to the group, and so on until everything has been awaited.You'd also need to introduce something like
ViewStoreTask
as a return type ofSend
that would makeawait
ing the action optional.This sounds perfectly doable to me. Please let me know if there is anything I'm missing here. Thanks!
Beta Was this translation helpful? Give feedback.
All reactions