Class: ReactOnRailsPro::AsyncPropsEmitter
- Inherits:
-
Object
- Object
- ReactOnRailsPro::AsyncPropsEmitter
- Defined in:
- lib/react_on_rails_pro/async_props_emitter.rb
Overview
Emitter class for sending async props incrementally during streaming render. Used by stream_react_component_with_async_props helper.
PROTOCOL:
Each call to emit.call(prop_name, value) sends an NDJSON line to the Node renderer:
{"bundleTimestamp": "abc123", "updateChunk": "(function(){...})()"}
The updateChunk JavaScript accesses the AsyncPropsManager via sharedExecutionContext and resolves the promise for that prop, allowing React to continue rendering.
WHY NOT USE GLOBAL VARIABLES? Global variables in Node.js VM persist across requests, causing data leakage. sharedExecutionContext is scoped to a single HTTP request (ExecutionContext).
PULL MODE:
When pull_enabled is true, React components can request props lazily via
getProp(). Those requests arrive as propRequest chunks on the response stream.
pull_requests exposes an Async::Queue that yields prop names as they arrive.
The user's block can dequeue and resolve them dynamically.
Constant Summary collapse
- SANITIZED_REJECTION_REASON =
"Async prop rejected by server"- CLOSED_REQUEST_STREAM_SOCKET_ERRORS =
Socket-level errors that mean the renderer request stream is already gone. These mirror the family that ReactOnRailsPro::Stream#log_client_disconnect (concerns/stream.rb) treats as a routine client disconnect, so the two files classify disconnect races the same way.
[ IOError, Errno::EPIPE, Errno::ECONNRESET, Errno::ECONNABORTED ].freeze
Instance Attribute Summary collapse
-
#pull_requests ⇒ Object
readonly
Returns the value of attribute pull_requests.
Class Method Summary collapse
-
.closed_request_stream_errors ⇒ Object
Exception classes that indicate a write landed on an already-closed request stream.
Instance Method Summary collapse
-
#call(prop_name, prop_value) ⇒ Object
Sends an async prop to the Node renderer.
-
#end_stream_chunk ⇒ Object
Generates the chunk that should be executed when the request stream closes.
-
#initialize(bundle_timestamp, request_stream, pull_enabled: false) ⇒ AsyncPropsEmitter
constructor
A new instance of AsyncPropsEmitter.
-
#reject(prop_name, reason) ⇒ Object
Rejects an async prop on the Node side so React can show an error boundary.
-
#render_complete! ⇒ Object
Called by stream_request when the response stream signals render complete.
Constructor Details
#initialize(bundle_timestamp, request_stream, pull_enabled: false) ⇒ AsyncPropsEmitter
Returns a new instance of AsyncPropsEmitter.
63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/react_on_rails_pro/async_props_emitter.rb', line 63 def initialize(, request_stream, pull_enabled: false) @bundle_timestamp = @request_stream = request_stream @pushed_props = Set.new # Latched the first time a write hits a closed request stream, so the rest of # the user's block (which runs in its own fiber and may keep emitting) is # skipped silently instead of logging once per remaining prop. @request_stream_closed = false @pull_enabled = pull_enabled @pull_requests = PullRequestQueue.new(@pushed_props) if pull_enabled end |
Instance Attribute Details
#pull_requests ⇒ Object (readonly)
Returns the value of attribute pull_requests.
61 62 63 |
# File 'lib/react_on_rails_pro/async_props_emitter.rb', line 61 def pull_requests @pull_requests end |
Class Method Details
.closed_request_stream_errors ⇒ Object
Exception classes that indicate a write landed on an already-closed request
stream. @request_stream is a Protocol::HTTP::Body::Writable::Output; writing
after its queue closes raises Protocol::HTTP::Body::Writable::Closed on a clean
close, or the stored socket error (Errno::*/IOError) that tore the stream down
on an aborted one (see async-http Output#passthrough -> Writable#write).
Resolved at rescue time rather than at load time on purpose: the protocol-http writable-body class is required lazily (only once streaming starts) and its Closed constant has moved namespaces across gem versions, so the defined? guard keeps this file loadable everywhere and picks the class up once it exists.
85 86 87 88 89 |
# File 'lib/react_on_rails_pro/async_props_emitter.rb', line 85 def self.closed_request_stream_errors return CLOSED_REQUEST_STREAM_SOCKET_ERRORS unless defined?(Protocol::HTTP::Body::Writable::Closed) CLOSED_REQUEST_STREAM_SOCKET_ERRORS + [Protocol::HTTP::Body::Writable::Closed] end |
Instance Method Details
#call(prop_name, prop_value) ⇒ Object
Sends an async prop to the Node renderer. The prop value is JSON-serialized and sent as an NDJSON line. On the Node side, this triggers asyncPropsManager.setProp(propName, value).
94 95 96 |
# File 'lib/react_on_rails_pro/async_props_emitter.rb', line 94 def call(prop_name, prop_value) write_settled_chunk(prop_name, action: "send") { generate_update_chunk(prop_name, prop_value) } end |
#end_stream_chunk ⇒ Object
Generates the chunk that should be executed when the request stream closes. This tells the asyncPropsManager to end the stream.
107 108 109 110 111 112 |
# File 'lib/react_on_rails_pro/async_props_emitter.rb', line 107 def end_stream_chunk { bundleTimestamp: @bundle_timestamp, updateChunk: generate_end_stream_js } end |
#reject(prop_name, reason) ⇒ Object
Rejects an async prop on the Node side so React can show an error boundary.
99 100 101 102 103 |
# File 'lib/react_on_rails_pro/async_props_emitter.rb', line 99 def reject(prop_name, reason) # Once the reject chunk is written, Ruby treats the prop as settled too. # That keeps duplicate pull requests filtered even if the JS manager is recreated. write_settled_chunk(prop_name, action: "reject") { generate_reject_chunk(prop_name, reason) } end |
#render_complete! ⇒ Object
Called by stream_request when the response stream signals render complete. Closes the pull_requests queue so dequeue returns nil.
116 117 118 |
# File 'lib/react_on_rails_pro/async_props_emitter.rb', line 116 def render_complete! @pull_requests&.close end |