Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Observables can be made async iterable by using a buffer #33

Open
bakkot opened this issue Jul 28, 2023 · 3 comments
Open

Observables can be made async iterable by using a buffer #33

bakkot opened this issue Jul 28, 2023 · 3 comments

Comments

@bakkot
Copy link
Contributor

bakkot commented Jul 28, 2023

RxJS observables are async iterable (implemented using an unbounded buffer).

There's some good discussion of considerations and tradeoffs linked from that thread. Something to consider here.

// click-and-drag handler
element.addEventListener('mousedown', async () => {
  for await (let { clientX, clientY } of element.on('mousemove').takeUntil(element.on('mouseup')) {
    console.log('moved mouse to', clientX, clientY);
    if (clientX > threshold) {
      break; // triggers `return` handler from the async iterator, which automatically unsubscribes
    }
  }
  console.log('mouse up or exceeded threshold');
});

is pretty cute.

@Jamesernator
Copy link
Contributor

I have found this useful in the past, however I do think it would probably be worth forcing users to pick a queueing strategy rather than just being async iterable by default. Like depending on what's done with the events I've found all of these queueing strategies useful:

  • Single-buffered, i.e. only the latest event is buffered between calls to .next()
    • This is more appropriate when only the most recent event/value is relevant, e.g. things like progress monitoring or following mouse position generally only need most recent event
  • Fully buffered
    • This is more appropriate when all intermediate values are important, e.g. for drawing a line with the mouse/pointer then all intermediate events are important to be part of the line
  • Unbuffered
    • This is appropriate where values should be ignored until the previous one is processed, e.g. in a game if some player action is performed it may simply ignore inputs until that action has finished resolving

@benlesh
Copy link
Collaborator

benlesh commented Sep 22, 2023

FWIW, flatMap (which is concatMap in RxJS terms) uses the exact same strategy as conversion to an async iterable does. They really aren't any different.

source.flatMap(async (v) => {
  await getSomething(v);
})
.subscribe({
  next: sideEffect
})

// is functionally identical to

for await (const v of source) {
  const result = await getSomething(v);
  sideEffect(result);
}

The major differences being that you're not allocating a Promise<IteratorResult<T>> per turn, and cancellation in the async iterable's case relies on being able to hit a break; line in the for await loop.

@benlesh
Copy link
Collaborator

benlesh commented Sep 22, 2023

FWIW: I'm 100% in favor of interop between different types. It's very useful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants