You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何必须添加延迟才能让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:前端订阅确认后再触发广播

通过前端在订阅成功后发送确认请求,服务器收到确认后再执行广播逻辑,确保消息不会丢失。

  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
  1. 更新路由,添加确认动作的路由:
# config/routes.rb
Rails.application.routes.draw do
  resources :grids do
    post 'confirm_subscription', on: :member
  end
end
  1. 修改前端视图,添加订阅成功回调:
<!-- 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>
  1. 移除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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 22:10:11