Its not that unintuitive once you understand async/await and how the tasks are resumed. Execution is yielded at an await. At that time the loop is not running anywhere, its not even in a sleep state. The loop "terminates" by never being resumed.
I'm assuming the loop would terminate with an exception... like `await events.pointermove` would raise some cancellation exception that would be silently captured.
Oh... on second thought I realize the exception would have to come from `event.pointermove` (for example), and that object doesn't know anything about the receiver or whether it should cancel. So yeah, this does seem problematic.
Are you worried about when the case when an exception should be thrown from the thing being awaited? I'm not familiar with Arbo but usually the promise holds on to the exception until its observed. It could be that Arbo is configured to ignore the exception if the task is already cancelled.
I feel like approach #3 could have been implemented without abro.or() and it would have been fine and had a little less magic. Though looking at the code I realize you'd need something like `await abro.any(events.pointermove, events.pointerup, windowEvents.keydown)` and a good way to determine which kind of event you got, and finally abro.any() doesn't exist. So maybe abro.or() (representing a group of complementary event handlers) is better than the alternative.
I like approach #1 the best. Sure, there are some bugs, but a state diagram would help make it clearer instead of introducing magic.