Another example relevant in node is continuation-local-storage (equivalent to threadlocal storage). Implementing it on top of generators or other "chainable / thenable" abstractions is trivially easy. Implementing it on top of native promises and async/await is impossible without deep hooks into the platform.
In the meantime generator based libraries would've properly explored the whole breadth of power that co-routines can give you, creating cowpaths to be paved by TC39.
Promises make trade-offs, and they end up with a design that is generally good and can be used well in some number of situations. But not all. Not nearly enough to get first class syntax support that makes them privileged over all other solutions.
Gorgi Kosev's post highlights a very nice use case for generators (database transactions). However, in all my JavaScript over the last few years, that is the only good use case I've found so far in my code base for generators. In all other use cases I've come across, async-await works just fine, and has a much nicer syntax to work with.
I elaborated that case in the most detail, but there are many other problems that generators solve mentioned in the blog post. Another very common one is getting the current user that initiated the request (or maybe their session), which you need to pass around to all your functions/classes.
What if you could simply `yield getCurrentUserSession` and the engine which ran the toplevel generator returned it back to you?
jhusein's compositional functions solved the syntax issue.
MobX is the most powerful front end development library on the market right now. It single-handedly solves the cache invalidation problem in a performant way, and its pluggable into any framework.
Slightly confusing api, no structural comparison, no adapters for popular frameworks (e.g. S.js-react or S.js-preact), no laziness (computations are recomputed even if they're not requested by reactions).
I don't want to use surplus because e.g. I want to use well developed UI components or toolkits like blueprintjs, which is implemented on top of React.
Slightly confusing is a real objection. MobX strives to implement transparent reactive programming, where the way you access, update and transform values works exactly like it would with regular objects. S.js has a worse learning curve.
> MobX strives to implement transparent reactive programming, where the way you access, update and transform values works exactly like it would with regular objects. S.js has a worse learning curve.
Hardly: reactive values are get/set functions, and you create new ones with S(() => ...). That's it.
MobX's attempt at transparency yields inescapable and surprising corner cases.
> Redundant computations are computed; [...] even though the completed() computed isn't used anywhere, the reduction is still being recomputed every time todo state changes
Incorrect. The todos binding is an SArray not a regular array [1]. See my modified version where I log the events to the console [2].
The number of tricky corner cases in MobX are very few. Also, once the implementation switches to proxies the vast majority will disappear.
I'm willing to accept a few corner cases as long as there is a large common subset of functionality that works both with and without a small number of decorators. This can be utilised to write models that can be used in both a reactive and a non-reactive context with a different set of decorators injected in. S.js looks too invasive to do this.
Your codepen has no completed todo count. Why are the recomputations logged every time here?
> Your codepen has no completed todo count. Why are the recomputations logged every time here?
Because you can't apply reduce any other way. It's a computation defined over a whole collection. To incrementalize it, you'd need to be able to invert whatever function you're trying to apply in order to arbitrarily undo and redo the operation as elements are added/removed. This is literally impossible in general as not all functions have inverses.
We use this to great extent in our application, by only rendering components that are visible in the viewport at the moment.
You can also keep its cached value alive, but not recompute it until needed by implementing a reaction that observes a computed but doesn't request its value. This will keep the entire computation graph cached but idle and partially dirty until the value is requested, at which point only stale dependencies will be recomputed. This can be extremely powerful: for example you can implement a state tree undo/redo by implementing serialize, then observing the serialize computed for the root item without requesting its value (keeping things cached) and only requesting recomputations when certain sufficient number of mutations are made. (with the vast number of reused values being structurally shared between undo/redo states)
> What would you call it? S.atomic? I don't see how the existing name is particularly unsuitable.
Yes atomic would be an improvement. Freeze only makes sense if you are thinking in terms of FRP signals in time, and its unclear whether the abstraction tries to hide its signal underpinnings or expose them (its somewhere inbetween)
Here is an example of how promises limit the power of mobx
https://twitter.com/spion/status/958906847385341952
Another example relevant in node is continuation-local-storage (equivalent to threadlocal storage). Implementing it on top of generators or other "chainable / thenable" abstractions is trivially easy. Implementing it on top of native promises and async/await is impossible without deep hooks into the platform.
More examples here: https://spion.github.io/posts/es7-async-await-step-in-the-wr... (see the second part)
We should've paused on async-await and waited for jhusain's compositional functions: https://github.com/jhusain/compositional-functions
In the meantime generator based libraries would've properly explored the whole breadth of power that co-routines can give you, creating cowpaths to be paved by TC39.
Promises make trade-offs, and they end up with a design that is generally good and can be used well in some number of situations. But not all. Not nearly enough to get first class syntax support that makes them privileged over all other solutions.