Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(ServerRendering): execution should be async #982

Merged
merged 2 commits into from
Feb 4, 2014
Merged

fix(ServerRendering): execution should be async #982

merged 2 commits into from
Feb 4, 2014

Conversation

bripkens
Copy link
Contributor

The documentation states that React.renderComponentToString
'uses a callback API to keep the API async', but the
implementation is actually synchronous. In order to maintain
this contract the callback should always be called
asynchronously.

@@ -50,7 +50,7 @@ function renderComponentToString(component, callback) {
transaction.perform(function() {
var markup = component.mountComponent(id, transaction, 0);
markup = ReactMarkupChecksum.addChecksumToMarkup(markup);
callback(markup);
window.setTimeout(callback, 0, markup);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

window doesn't necessarily exist where you might call this.

@bripkens
Copy link
Contributor Author

Very true, sorry for that. Updated the PR accordingly.

@syranide
Copy link
Contributor

@bripkens FYI, I think you forgot to push it.

@@ -50,7 +50,7 @@ function renderComponentToString(component, callback) {
transaction.perform(function() {
var markup = component.mountComponent(id, transaction, 0);
markup = ReactMarkupChecksum.addChecksumToMarkup(markup);
callback(markup);
setTimeout(callback, 0, markup);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you change this to setTimeout(() => callback(markup), 0) ? Add FB we disallow to use the third argument for setTimeout like this.

@bripkens
Copy link
Contributor Author

Done. CLA is signed as well.

@johnthethird
Copy link

I recently implemented server-side rendering in Rails here:

https://github.com/johnthethird/react-rails/blob/server-rendering/lib/react/rails/view_helper.rb#L71

It is a lot easier if renderComponentToString is fake-async like it used to be. Getting data out of a JS VM like V8 or Rhino (Java) is difficult when it comes to async APIs. For example ExecJS which Rails/Sprockets uses to wrap different JS VMs does not support it at all.

What if renderComponentToString, in addition to the truly async callback, also just plain returned the HTML?

@petehunt
Copy link
Contributor

What if we just committed to a sync API? Do we think this is likely to realistically change in the near term @sebmarkbage @jordwalke ? I know we've talked about async rendering but it seems pretty far off.

@cpojer cpojer closed this Jan 29, 2014
@cpojer cpojer reopened this Jan 29, 2014
@cpojer
Copy link
Contributor

cpojer commented Jan 29, 2014

woops, sorry, don't ask me what happened there.

@sebmarkbage
Copy link
Collaborator

@petehunt I'd be fine with that. A short term async rendering would likely be of the continuation style or allow certain components to render a partial result and then update themselves. Longterm, however, we could do parallelized rendering with PJS. All could be opt-in.

@@ -50,7 +50,7 @@ function renderComponentToString(component, callback) {
transaction.perform(function() {
var markup = component.mountComponent(id, transaction, 0);
markup = ReactMarkupChecksum.addChecksumToMarkup(markup);
callback(markup);
setTimeout(() => callback(markup), 0);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

process.nextTick would be more useful on the server...? why zero timeout?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't mean to restrict the server side rendering to node.js. For instance, I am currently trying to make this work on the JVM where process.nextTick is not available.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a lot more people use node than other server side js environments, and even then, could shim this to use node conventions as default, falling back to setTimeout for other platforms. (very personal bias though!)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The semantic difference between process.nextTick and setTimeout seems negligible in this case (while it might not be for edge cases). I would therefore argue that compatibility is more important than Node conventions (especially considering that renderComponentToString is part of React's default distribution and not part of some server side add-on).

Anyhow, let's wait until the core team has made up their mind w.r.t. asynchronous rendering in general :-).

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

true that, anything that runs on client would need to avoid nodeisms. I think I saw somewhere a comment about using setImmediate..? there's a nice discussion of the problem with setTimeout's clamped minimum values vs real nextTick deferral here: http://jphpsf.github.io/setImmediate-shim-demo/

@bripkens
Copy link
Contributor Author

Maybe I should provide some context and why I think async rendering is interesting.

Over the last two weeks I have been playing with the idea of server side rendering. Rendering simple component trees is easy with the current version of renderComponentToString, but many applications will probably need to retrieve some data in order to display it. As of now, the implementation of renderComponentToString is not capable of handling such cases and may therefore be synchronous (as proposed by @petehunt).

I stumbled over this issue while making facebookarchive/react-page#47 work on the JVM. On the JVM I have complete control over the event loop and can identify the moment when all data has been loaded. I am hoping to make server side rendering work without any changes to an existing React application. That being said, more React changes will be necessary in order to support this "advanced" server side rendering (and then the question remains whether these changes are portable across JS environments).

@bripkens
Copy link
Contributor Author

Just noticed #985.

Supporting server side rendering cases with async data retrieval is surely going to require a fair amount of work. If you want to make the API synchronous and thus not focus on this issue now, I have no problem with that.

@andreypopp
Copy link
Contributor

On a related note, what about changing the callback signature to callback(err, markup) like in Node.js and propagate all errors through the callback? It's a little bit inconsistent, I think, to expect a result in a callback and an error via try ... catch ... statement. What do you think?

@dazld
Copy link

dazld commented Jan 30, 2014

following node conventions seems like a good idea.

@petehunt
Copy link
Contributor

I think that data fetching is workable even in a sync world. You can just move the asynchronicity outside of React. You can even put the code for data fetching alongside your React component, just don't call render() until all the data is fetched and pass the data in as a top-level prop.

I think making this method synchronous would be better.

@bripkens
Copy link
Contributor Author

bripkens commented Feb 2, 2014

Would you like me to update my PR accordingly, i.e. change the API to synchronous, or do you wish to wait with this (breaking) change? Would love to help you out here.

@petehunt
Copy link
Contributor

petehunt commented Feb 2, 2014

@bripkens if you want to make the API synchronous that would be awesome!

The documentation states that React.renderComponentToString
'uses a callback API to keep the API async', but the
implementation is actually synchronous. In order to maintain
this contract the callback should always be called
asynchronously or be change to a synchronous API.

As per the discussion of pull request 982, the API should
be changed to a synchronous one.
@bripkens
Copy link
Contributor Author

bripkens commented Feb 3, 2014

@petehunt PR updated. API is no synchronous and provides some guidance for migrations.

// We use a callback API to keep the API async in case in the future we ever
// need it, but in reality this is a synchronous operation.

function renderComponentToString(component) {
invariant(
ReactComponent.isValidComponent(component),
'renderComponentToString(): You must pass a valid ReactComponent.'
);

invariant(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that this should be an invariant... do we want to stop execution or should we let it keep running and just warn? @petehunt?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am cool with this

@bripkens
Copy link
Contributor Author

bripkens commented Feb 4, 2014

Sure thing, here you go. The word "internal" might imply that it is not, but is this typechecker available somewhere? You made me curious ;-).

@petehunt
Copy link
Contributor

petehunt commented Feb 4, 2014

It's not available at this point, sorry :/

petehunt added a commit that referenced this pull request Feb 4, 2014
fix(ServerRendering): execution should be async
@petehunt petehunt merged commit 3642b6e into facebook:master Feb 4, 2014
@plievone
Copy link
Contributor

plievone commented Feb 5, 2014

@bripkens
Copy link
Contributor Author

bripkens commented Feb 6, 2014

It does, yes. I opened PR #1033 with the necessary doc changes.

josephsavona added a commit that referenced this pull request May 15, 2024
See the background in #982. This PR reimplements part of InferReactiveScopes, 
aligning reactive scopes to block boundaries, but against ReactiveFunction 
instead of the HIR.
josephsavona added a commit that referenced this pull request May 15, 2024
See the background in #982. This PR reimplements part of InferReactiveScopes, 
merging overlapping reactive scopes, but against ReactiveFunction instead of the 
HIR.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants