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"
Instance Attribute Summary collapse
-
#pull_requests ⇒ Object
readonly
Returns the value of attribute pull_requests.
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.
- #pull_enabled? ⇒ Boolean
-
#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.
55 56 57 58 59 60 61 |
# File 'lib/react_on_rails_pro/async_props_emitter.rb', line 55 def initialize(, request_stream, pull_enabled: false) @bundle_timestamp = @request_stream = request_stream @pushed_props = Set.new @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.
53 54 55 |
# File 'lib/react_on_rails_pro/async_props_emitter.rb', line 53 def pull_requests @pull_requests 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).
70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
# File 'lib/react_on_rails_pro/async_props_emitter.rb', line 70 def call(prop_name, prop_value) update_chunk = generate_update_chunk(prop_name, prop_value) @request_stream << "#{update_chunk.to_json}\n" @pushed_props.add(prop_name) rescue StandardError => e # Continue streaming: one failed async prop write should not abort the # entire render. The prop is not marked as pushed unless the write # succeeds, so pull mode can request it again instead of silently hanging. Rails.logger.error do backtrace = e.backtrace&.first(5)&.join("\n") "[ReactOnRailsPro::AsyncProps] Failed to send async prop '#{prop_name}': " \ "#{e.class} - #{e.}\n#{backtrace}" end 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.
102 103 104 105 106 107 |
# File 'lib/react_on_rails_pro/async_props_emitter.rb', line 102 def end_stream_chunk { bundleTimestamp: @bundle_timestamp, updateChunk: generate_end_stream_js } end |
#pull_enabled? ⇒ Boolean
63 64 65 |
# File 'lib/react_on_rails_pro/async_props_emitter.rb', line 63 def pull_enabled? @pull_enabled end |
#reject(prop_name, reason) ⇒ Object
Rejects an async prop on the Node side so React can show an error boundary.
86 87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/react_on_rails_pro/async_props_emitter.rb', line 86 def reject(prop_name, reason) update_chunk = generate_reject_chunk(prop_name, reason) @request_stream << "#{update_chunk.to_json}\n" # 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. @pushed_props.add(prop_name) rescue StandardError => e Rails.logger.error do backtrace = e.backtrace&.first(5)&.join("\n") "[ReactOnRailsPro::AsyncProps] Failed to reject async prop '#{prop_name}': " \ "#{e.class} - #{e.}\n#{backtrace}" end end |
#render_complete! ⇒ Object
Called by stream_request when the response stream signals render complete. Closes the pull_requests queue so dequeue returns nil.
111 112 113 |
# File 'lib/react_on_rails_pro/async_props_emitter.rb', line 111 def render_complete! @pull_requests&.close end |