5 ms·
So glad someone captured this talk! At the 15 minute mark, he introduces a new paradigm for asynchronous IO within render functions; see a screenshot here: ht
by btown 9y ago
So glad someone captured this talk!
At the 15 minute mark, he introduces a new paradigm for asynchronous IO within render functions; see a screenshot here:
https://www.dropbox.com/s/wybqgtftuyqipk8/Screenshot%202018-03-01%2009.41.11.png?dl=0 https://www.dropbox.com/s/wybqgtftuyqipk8/Screenshot%202018-.... This is groundbreaking, to say the least, as previously this needed to be done by adding higher-order components to inject the result of the fetch as a prop at some higher level; now, it's as simple as changing the src property in a single line of code.
One of Dan's colleagues, Andrew Clark, described how this is implemented on Twitter: https://twitter.com/acdlite/status/969171217356746752 https://twitter.com/acdlite/status/969171217356746752
Here's how suspending works:
- in the render method, read a value from the cache.
- if the value is already cached, the render continues like normal
- if the value is not already cached, the cache *throws a promise*
- when the promise resolves, React retries where it left off
Digging into the source code released in this companion tweet, imageFetcher.read() indeed throws (instead of an exception) a Promise that is apparently caught by React's new error handling infrastructure and handled somewhere up the chain. https://github.com/facebook/react/blob/master/packages/simple-cache-provider/src/SimpleCacheProvider.js#L187 https://github.com/facebook/react/blob/master/packages/simpl...
The talk indicated that there will be tons of support for granularly choosing when/how to show spinners, allowing interactivity with the rest of the interface, etc. while this loading is ongoing.
It's a bit magical, but promising to say the least!
- bajablast 9y agoSo I am guessing if a prop is `instanceof Promise` or some other constructor then it must trigger this response . . . is that correct?
- acemarke 9y agoThe key phrase is "thenable", which is a term for anything where `typeof variable.then === "function"` is true.
- wereHamster 9y agoBut only when passed to HTML elements I hope. Because say I have my own type of the shape `{ then(): Whatever }` and try to pass that as a prop to one of my components I don't want React to invoke that function.
- acemarke 9y agoIt's not about passing props - the "thenable" detection comes into play if you throw a promise (or something shaped like a promise) from within `render()`.
- wereHamster 9y agoWait, can render functions now throw, legally?
- timdorr 9y agoYup. This builds on error boundaries and componentDidCatch: https://reactjs.org/blog/2017/07/26/error-handling-in-react-16.html https://reactjs.org/blog/2017/07/26/error-handling-in-react-...
- danabramov 9y agoYes. (For this use case.)
- btown 9y agoReact isn't changing any logic of the types of props; if you actually pass a Promise as a prop, it will be treated 100% as it was in previous React versions. What's happening instead is that imageFetcher.read() either returns a string OR throws a promise; it never returns a Promise. So <Component foo={fooFetcher.read(...)} /> either gets instantiated with the fetched Foo, or throws so React.createElement(Component, ...) never gets called in the first place. Only if you physically throw a Promise (or "thenable") does the new logic apply: https://github.com/facebook/react/pull/12279/files#diff-1996f2b11f9c68c0a81652e32be88ddbR132 https://github.com/facebook/react/pull/12279/files#diff-1996... is called inside an exception handler.
- pault 9y agoWow, I never would have considered throwing anything other than an Error. On the face of it, this seems like an incredibly powerful technique, but is it an abuse of a language feature?
- riquito 9y agoIs a feature of the exceptions to let you handle exceptional cases far away from the caller, up in the stack. You should keep a distinction between exceptions that means "error" and exceptions that means "some kind of event happened", and handle them accordingly.
- acjohnson55 9y agoMy guess is that it won't catch on in the long run. But, you never know.
- btown 9y agoPython “abuses” exceptions in a similar way every time you iterate over a generator: https://softwareengineering.stackexchange.com/questions/112463/why-do-iterators-in-python-raise-an-exception https://softwareengineering.stackexchange.com/questions/1124... At the end of the day it’s just a control flow mechanism. And theoretically, if you lack data needed to render, it’s an exceptional condition.
- ocfx 9y agoIs the cache global?
- danabramov 9y agoIt's up to you. I intentionally kept moving parts to the minimum in my demo, but here's the real thing I was using behind the scenes: https://github.com/facebook/react/tree/master/packages/simple-cache-provider https://github.com/facebook/react/tree/master/packages/simpl.... So in that sense it's not global, you can create as many as you like and then isolate them (e.g. per subtree). The `simple-cache-provider` is just a reference implementation though. It's somewhat naïve, e.g. it doesn't have invalidation strategy and just lets you replace the whole cache with a new one if you want to invalidate. We'll publish more details on lower level API and how to write your own cache in the future.
- btown 9y agoIt seems from https://github.com/facebook/react/blob/master/packages/simple-cache-provider/src/SimpleCacheProvider.js#L256 https://github.com/facebook/react/blob/master/packages/simpl... that in the live version you'll be specifying which cache to use; this could be global, provided via React's old or new Context APIs, or passed down through props. And you could always write your own fetcher factory if you want to do something really custom; there's nothing special about the SimpleCacheProvider, and it doesn't use any React internals.
- danabramov 9y agoFWIW my demo code looked like export let cache; function initCache() { cache = createCache(initCache); } initCache(); export function createFetcher(fetch) { const res = createResource(fetch); return { read(...args) { return res(cache, ...args); }, }; }
- deleted 9y ago[deleted]
- pault 9y agoThe description of suspending sounds exactly like yielding generators and/or async await; why not build this feature on top of existing language constructs? I am quite sure the react core team has put more than the 30 seconds of thought I have into this question, so I'm curious what their reasoning is for using this implementation.
- bsimpson 9y ago> throws a promise > ... > It's...promising to say the least! ^ this guy :finger_guns:
- acjohnson55 9y agoThis seems to me like a workaround for the fact that React requires `render` methods to return React elements. And I suppose it also takes advantage of the fact that the `read` method can `throw` its way out of your `render` function, obviating the need to check its result and manually `return`. But not gonna lie, I'm pretty suspicious of overreliance on creative use of `throw`ing as something that is going to cross through application code's control flow. But perhaps it will prove convenient enough to be worth it.