6 ms·
>That seems a clear problem with the server side examples as presented in the RFC: they use synchronous node 'fs' instead of FS Promises It isn't synchronous,
by danabramov 6y ago
>That seems a clear problem with the server side examples as presented in the RFC: they use synchronous node 'fs' instead of FS Promises
It isn't synchronous, it only looks so. It actually uses FP Promises under the hood[1]. We're doing a bit of a trick there (it throws a special value the first time, and then React retries when it's resolved). Then the result gets cached, and the repeated render succeeds synchronously. We will describe this mechanism in detail in a future RFC.
All of the examples in the demo (including fetch, readFile, and the DB query call) are async under the hood.
However, they do leave us the ability to use sync I/O in the cases where it makes sense (depends on whether we have other work to do, etc).
[1]: https://github.com/facebook/react/blob/6cbb9394d1474e3a728b49dc7f3a11d61a421ce3/packages/react-fs/src/ReactFilesystem.js#L13 https://github.com/facebook/react/blob/6cbb9394d1474e3a728b4...
- dfabulich 6y agoYes, and that's a problem: there's no way to know when you call react-fs/react-fetch/etc whether it will be synchronous or asynchronous. async/await would make this code substantially clearer. (To be clear, right now the code is using a, uh, very surprising pattern to make asynchronous code appear synchronous: if the result value is cached, it returns the value synchronously, but if not, the fetcher throws a Promise. You know, you'd normally throw exception objects, but JS lets you throw any value, so why not throw a Promise amirite?!? When React catches a Promise (a thenable) it awaits the result, caches it and then re-runs the React component; now the component won't throw a Promise and will run to completion normally.) The FAQ says extremely little about why async/await were avoided: > Why don’t use just use async/await? > We’d still need a layer on top, for example, to deduplicate fetches between components within a single request. This is why there are wrappers around async APIs. You will be able to write your own. We also want to avoid delays in the case that data is synchronously available -- note that async/await uses Promises and incurs an extra tick in these cases. A layer to deduplicate fetches sounds great, but that library could use async/await, too. Using async/await will make this code substantially easier to understand, and the cost of a "tick" is trivial (and certainly worth the price, particularly in server-side code). EDIT: Thinking about this a bit harder, I know the React team has been extremely resistant to async/await in components for years now. Fine. But that needs its own RFC. Some clear written document spelling out in detail why async/await is the wrong approach for React, and not just a comment on this RFC. I'd like to ask the React team to write that RFC doc because I think it can't be written: you'll find that the argument falls apart when you try to explain it.
- danabramov 6y agoWe will definitely do the RFC in the future. Challenge accepted. :-)
- no_wizard 6y agoI know this is old now by a day but to add on, Is it possible you could document why then react doesn’t handle plan promises natively? You can conditionally return a promise there by not incurring the cost of the sugar of async/await Also related: why wouldn’t generators be a good fit for some of these use cases? Especially since iterating over data is a common action. I imagine this would be easier to integrate into the framework for things like this
- samc98 6y ago> why wouldn’t generators be a good fit for some of these use cases? Especially since iterating over data is a common action. I imagine this would be easier to integrate into the framework for things like this I think this is answered by Seb, in the "Contributing to Fiber" issue in the React Repo: https://github.com/facebook/react/issues/7942#issuecomment-254987818 https://github.com/facebook/react/issues/7942#issuecomment-2... But maybe things have changed since then! I suspect that "why doesn't react handle plain promises natively" would be addressed in the RFC detailing the whole "let's throw a promise" thing.
- dfabulich 6y agoThe Server Components FAQ has been updated. It now says: > Why not use async/await? > The React IO libraries used in the demo and RFC follow the conventions we've discussed previously for writing Suspense-compatible data-fetching APIs. Suspense-compatible APIs return data synchronously when it is already available, throw if there is an error, or "suspend" to indicate to React that they are unable to return a value. The mechanism for Suspending is to throw a Promise value. React uses resolution of the promise to know when the API may be ready to provide a value (or that it has failed) and to schedule an attempt to render the component again. > One new consideration in the design of Suspense from this proposal is that we would like to use a consistent API for accessing data across Server Components, Client Components, and Shared Components. Overall, though, the design of Suspense is outside the scope of this RFC. We agree that we should document this design clearly and will prioritize doing so in the new year. Thanks for incorporating this; let's see how the design doc plays out next year.
- brundolf 6y ago> It isn't synchronous, it only looks so. It actually uses FP Promises under the hood[1]. We're doing a bit of a trick there (it throws a special value the first time, and then React retries when it's resolved). Then the result gets cached, and the repeated render succeeds synchronously. I don't mean to offend, but this level of magic behavior sounds terrifying to me. React's original slogan was "it's just JavaScript". That made it easier to understand without special knowledge, easier to pair with whichever libraries you wanted to use, and easier to apply tooling to without special integration. But it seems to be getting further and further away from that goal with each passing year, turning into an opinionated, all-in-one framework like Angular. Maybe this is just the cost of gaining and maintaining popularity. I won't say it's intrinsically a bad thing, but I personally don't feel good about it.
- eyelidlessness 6y ago> I don't mean to offend, but this level of magic behavior sounds terrifying to me. React's original slogan was "it's just JavaScript". That made it easier to understand without special knowledge, easier to pair with whichever libraries you wanted to use, and easier to apply tooling to without special integration. But it seems to be getting further and further away from that goal with each passing year, turning into an opinionated, all-in-one framework like Angular. I don’t know if this is that kind of magic (I certainly feel like hooks are, though they still don’t make React feel as much like a framework as Angular). When I saw Promise references in JSX, it looked a little confusing at first, but I thought about how it would probably be trivial to write a createElement wrapper that does the same thing on the client, just wrapping in a Suspense or similar until all of the promises resolve. I’m sure it’s more complicated than that, but it does still seem like “it’s just JavaScript”. Promises are widely used in JS already, after all.
- brundolf 6y agoSee for me hooks - while a bit obtuse and weird - are not this kind of magic because they come down to just function calls. You can use almost nothing but your existing knowledge of JavaScript and reverse-engineer what's going on. That isn't the case with a lot of this other stuff
- WorldMaker 6y ago> It isn't synchronous, it only looks so. Ah, all the more dangerous then. I appreciate that throwing promises is a clever flow control hack and that y'all have proven it to be a useful micro-optimization, at least in current V8. But my experience tells me that I'd rather a junior developer write incorrect async/await code than accidentally introduce a synchronous dead-lock. Async code that looks synchronous can mask synchronous code that should be async much easier. > However, they do leave us the ability to use sync I/O in the cases where it makes sense async/await don't stop you from doing that either. The .NET CLR/BCL has a lot of optimizations in place today for fast-pathing synchronous Task<T> (and now ValueTask<T>) code. As Promises become more and more common in the JS ecosystem it should be expected that more and more such optimizations will arrive in JS engines "tomorrow". Even if we don't feel like we can expect such optimizations soon, server side code is obviously easier to scale for the application developer and less needful of such micro-optimizations, and I think adds more weight to my gut instinct that explicitly async (async/await) code on server side would possibly be one good way to make a distinction between client and server-side only code.