Turbo Stream与Active Job广播存在竞态条件,客户端错过更新求助
解决Turbo Stream广播早于WebSocket连接的竞态条件问题
问题场景
当前通过Turbo Stream实现WebSocket更新DOM,配置如下:
视图代码
<%= turbo_stream_from "connections_notifications" %>
控制器触发Job
PrepSidenavJob.perform_later(current_user)
Job广播逻辑
Turbo::StreamsChannel.broadcast_replace_to( "connections_notifications", target: "connections_notification", partial: 'product/sidenav_badge', locals: {count: connections_notification}, layout: nil )
由于Job执行速度快于客户端WebSocket连接建立,出现如下竞态顺序:
- 渲染初始模板
- 提交Job
- Job执行处理
- Job完成广播
- 客户端连接到
/cable - 客户端未收到任何更新
使用版本:Rails 7.2、@hotwired/turbo-rails 8.0.12
解决思路
1. 延迟Job执行
给Job添加延迟,预留客户端建立WebSocket连接的时间。触发Job时指定延迟时长:
PrepSidenavJob.perform_later(current_user).wait_until(2.seconds.from_now)
注意:延迟时长需根据实际环境调整,避免过长影响用户体验
2. 初始状态直接渲染
不在Job中处理初始状态的广播,而是在视图渲染时直接输出最新的connections_notification数据,后续数据变化再通过WebSocket广播:
<div id="connections_notification"> <%= render 'product/sidenav_badge', count: current_user.connections_notification %> </div> <%= turbo_stream_from "connections_notifications" %>
这种方式从根源上避免初始状态的竞态问题,初始内容无需依赖Socket连接。
3. 客户端连接后主动拉取数据
监听Turbo的连接事件,在客户端成功建立WebSocket连接后,主动发起请求获取最新状态:
document.addEventListener('turbo:connect', () => { fetch('/api/connections_notification') .then(res => res.text()) .then(html => { document.getElementById('connections_notification').innerHTML = html; }); });
需要对应新增一个API接口返回badge的HTML片段。
4. 自定义Channel主动推送初始数据
将频道改为用户专属,在客户端连接成功时,主动推送一次最新状态:
# 自定义Channel class ConnectionsNotificationsChannel < Turbo::StreamsChannel def subscribed stream_for current_user # 连接成功后发送最新的badge数据 broadcast_replace_to( current_user, target: "connections_notification", partial: 'product/sidenav_badge', locals: {count: current_user.connections_notification}, layout: nil ) end end
视图中修改订阅代码:
<%= turbo_stream_from current_user, channel: "ConnectionsNotificationsChannel" %>
这样无论Job执行时机如何,客户端连接后都会收到最新数据,同时后续的更新也能正常广播。
内容的提问来源于stack exchange,提问作者jsharpe
相关产品推荐
相关产品推荐

