One approach is here: https://observablehq.com/@bobkerns/livedata
Sorry for the poor state of documentation and comments; I just did a rushed rework before publishing it; I’ll try to clean it up in the next few days.
The basic idea is to wrap a function of n arguments to get a new function that takes n generators (or promises of same), returning a new async generator. Then you use this to arrange to always return a generator to the top level, so it can handle invalidation. (You could use
invalidation.then(() => gen.return()) of course, but I haven’t thought of a case where that would be preferable to just letting the top level handle it.
Every time next() is called on the resulting generator, the function is called on the (resolved) next() values on the generators.
The underlying mechanism gives options for terminating on the first generator’s completion, or letting finished ones become undefined, or to fill in with the last value seen until all have terminated.
This is particularly useful when supplying some constant values, as they become generators of that constant value.
If you invoke return() or throw() on the composite generator, the same is done to all of the input generators. (Yes, including throw, which is done on each before the composite one itself throws).
It needs more testing, and obviously, documentation, but I thought I’d put it out for discussion since it’s a current topic.
I’d also like a version that calls the function whenever any of the input generators yields a new value.