bikeshaving / crank

The Just JavaScript Framework
https://crank.js.org
MIT License
2.7k stars 75 forks source link

Allow async generator components to use `for... of` #243

Closed brainkim closed 1 year ago

brainkim commented 1 year ago

There are two disadvantages with the way async generator components are currently designed:

  1. Errors thrown by async generator components don’t have stack traces which point to the the initiators of rendering (usually a call to render() or ctx.refresh()). This is because async generator components resume by unblocking the for await... of loop, rather than via calls to iterator.next().

  2. The differences in behavior between sync generator components and async generator components can be counter-intuitive for newcomers (TODO: find the issue/discussion). And even if you’re familiar with the differences, having to refactor to use for await and switch your mental model so that you can await a promise directly in a stateful component seems like a high cost relative to the benefits.

For instance, one of the big reasons I initially wanted this is that I wanted easy rendering post-conditions, i.e. code which runs after the yield. This is useful but also limited: code after yields only runs after recursive child renders, which isn’t really a useful time to run code, and even if the async generator component does not render asynchronously, the real moment you’re trying to run code is not usually “when the component finishes rendering” but “when the component actually insert rendered children into the active document.”

To be clear, I don’t think async generator components are mis-designed. I still think using for await... of is the best way to implement fallbacks states, and the idea of a component yielding multiple trees per update is elegant. Nevertheless, I do think the cons listed above are important to address.

One of the main themes around the upcoming 0.5 release has been about making things easier to type, and I’ve found a lot of success in inspecting the internal state of components via the props iterators to implement quality of life conveniences. For instance, . This gives me hope that maybe we can allow async generator components to use for... of iterators. The idea would be that if we yield in a for... of loop, we switch to behavior that mimics sync generator components, just async.

This was never impossible: async functions can call sync functions. The one thing I worry about is that now async generator components have three de facto modes: 1. while looping (outside any render loop), 2. for of looping, 3. for await of looping. Is this itself confusing? Not sure.

brainkim commented 1 year ago

for...of loops in async generator components don’t seem to be throwing correctly, so this is even more important.

brainkim commented 1 year ago

Added in 0.5.0