为何必须添加延迟才能让Turbo Stream正常更新?
Turbo Stream前端无更新问题:必须在Ruby端加延迟才能生效
问题现象
Turbo的JavaScript端响应存在延迟问题,必须在Ruby端添加1秒延迟才能让页面上的Turbo Stream正常更新。若不添加该延迟,尽管日志显示所有Turbo Stream已渲染、任务已执行并传输,但页面完全没有更新。
相关代码
控制器代码
# app/controllers/grids_controller.rb class GridsController < ApplicationController def play @job_id = PlayJob.perform_later(**@grid_data.attributes).job_id # Turbo broadcasts render :play, status: :created end end
Turbo Stream视图
<!-- app/views/grids/play.html.erb --> <%= turbo_stream_from :play, @job_id %> <div id="grid"> <% unless Rails.env.test? %> <p><strong>Phase 0</strong></p> <div class="cells"> <%= image_tag asset_url('loading.gif'), alt: 'loading', class: 'loading' %> </div> <% end %> </div>
Turbo广播代码
# lib/grid.rb class Grid def play(job_id) sleep 1 # 必须添加1秒延迟才能让Turbo响应! loop do broadcast_to :play, job_id break if phase >= @phases sleep @phase_duration next_phase end end def broadcast_to(*streamable) Turbo::StreamsChannel.broadcast_update_to( streamable, target: 'grid', partial: 'grids/grid', locals: { grid: self } ) end end
问题根源
后台PlayJob启动后立即发送广播,但此时前端通过turbo_stream_from建立的Turbo Stream订阅还未完成连接,导致第一条广播消息被前端错过。硬编码的sleep 1是在赌订阅建立的时间,属于不可靠的临时解决方案。
解决方案
方案1:前端订阅确认后再触发广播
通过前端在订阅成功后发送确认请求,服务器收到确认后再执行广播逻辑,确保消息不会丢失。
- 修改控制器,新增订阅确认动作:
# app/controllers/grids_controller.rb class GridsController < ApplicationController def play @job = PlayJob.perform_later(**@grid_data.attributes) @job_id = @job.job_id render :play, status: :created end def confirm_subscription job = PlayJob.find(params[:job_id]) # 触发Job中的广播逻辑(根据Job实际实现调整) grid = Grid.new(**job.arguments.first) grid.play(params[:job_id]) head :ok end end
- 更新路由,添加确认动作的路由:
# config/routes.rb Rails.application.routes.draw do resources :grids do post 'confirm_subscription', on: :member end end
- 修改前端视图,添加订阅成功回调:
<!-- app/views/grids/play.html.erb --> <%= turbo_stream_from :play, @job_id, callback: "confirmSubscription('#{@job_id}')" %> <div id="grid"> <% unless Rails.env.test? %> <p><strong>Phase 0</strong></p> <div class="cells"> <%= image_tag asset_url('loading.gif'), alt: 'loading', class: 'loading' %> </div> <% end %> </div> <script> function confirmSubscription(jobId) { const csrfToken = document.querySelector('meta[name="csrf-token"]').content; fetch(`/grids/${jobId}/confirm_subscription`, { method: 'POST', headers: { 'X-CSRF-Token': csrfToken, 'Content-Type': 'application/json' } }); } </script>
- 移除Grid类中的硬编码延迟:
# lib/grid.rb class Grid def play(job_id) loop do broadcast_to :play, job_id break if phase >= @phases sleep @phase_duration next_phase end end # 其他代码保持不变 end
方案2:通过Action Cable连接状态判断订阅是否就绪
在Turbo StreamsChannel中记录已订阅的job_id,Job定期检查该job_id是否有活跃订阅者,再开始广播。这种方式适合不需要即时响应的场景。
总结
硬编码延迟是不可靠的,会因网络环境差异导致问题复现。通过前端确认订阅完成的机制,能确保广播消息在订阅就绪后发送,从根本上解决消息丢失的问题。
内容的提问来源于stack exchange,提问作者Chiara Ani
相关产品推荐
相关产品推荐

