在 redux-Saga 中重新连接到 webSocket 服务器的标准方法?

Posted

技术标签:

【中文标题】在 redux-Saga 中重新连接到 webSocket 服务器的标准方法?【英文标题】:Standard way of reconnecting to webSocket server in redux-Saga? 【发布时间】:2020-01-21 09:50:16 【问题描述】:

我正在尝试使用 redux-saga 从我的 react App 连接到 websocket 服务器并希望捕获连接丢失(服务器错误,重新启动),以便每隔 4 秒重新连接一次,直到连接再次恢复。问题在于重新连接到 webSocket,redux 存储不再更新。

我尝试按照以下代码使用 redux-saga 的 eventChannel。不幸的是,没有或至少我找不到任何文档回答 ws reconnect in redux-saga

import eventChannel from 'redux-saga';
import all, takeEvery, put, call, take, fork from 'redux-saga/effects'
import INITIALIZE_WS_CHANNEL from "../../constants/ActionTypes"
import updateMarketData from "../actions"


function createEventChannel() 
    return eventChannel(emit => 

        //Subscribe to websocket
        const ws = new WebSocket('ws://localhost:9000/rates');

        ws.onopen = () => 
            console.log("Opening Websocket");
        ;

        ws.onerror = error => 
            console.log("ERROR: ", error);
        ;

        ws.onmessage = e => 
            return emit(data: JSON.parse(e.data))
        ;

        ws.onclose = e => 
            if (e.code === 1005) 
                console.log("WebSocket: closed");
             else 
                console.log('Socket is closed Unexpectedly. Reconnect will be attempted in 4 second.', e.reason);
                setTimeout(() =>  
                    createEventChannel();
                , 4000);
            
        ;

        return () => 
            console.log("Closing Websocket");
            ws.close();
        ;
    );


function * initializeWebSocketsChannel() 
    const channel = yield call(createEventChannel);
    while (true) 
        const data = yield take(channel);
        yield put(updateMarketData(data));
    


export function * initWebSocket() 
    yield takeEvery(INITIALIZE_WS_CHANNEL, initializeWebSocketsChannel);


export default function* rootSaga() 
    yield all ([
        fork(initWebSocket)
    ]);


更新

为了完成@azundo 为寻找 websocket 和 redux-saga 完整示例的人接受的答案,我添加了以下代码:

function * initializeWebSocketsChannel() 
    console.log("going to connect to WS")
    const channel = yield call(createEventChannel);
    while (true) 
        const data = yield take(channel);
        yield put(updateMarketData(data));
    


export function * startStopChannel() 
    while (true) 
        yield take(START_CHANNEL);
        yield race(
            task: call(initializeWebSocketsChannel),
            cancel: take(STOP_CHANNEL),
        );
        //if cancel wins the race we can close socket
        ws.close();
    


export default function* rootSaga() 
    yield all ([
        startStopChannel()
    ]);


START_CHANNELSTOP_CHANNEL 动作可以在 componentDidMount中调用>componentWillUnmount 分别是 react 组件的生命周期。

【问题讨论】:

我看到了你的回答,使用 componentDidMount 和 componentWillUnmount 听起来是个好主意,但是如果我们在 componentDidMount 上断开连接,一段时间后我们想再次连接怎么办? 【参考方案1】:

这不起作用的原因是因为您对createEventChannel 的递归调用没有被yielded 到传奇中间件redux-saga 无法知道后续事件通道的创建。您可能希望在事件通道中定义递归函数,请参见下面的代码,因此只有一个 eventChannel 始终挂接到存储中。

还要注意在预期的套接字关闭时添加了发射 END,这样如果您不重新连接,就不会让 eventChannel 永远打开。

import eventChannel, END from 'redux-saga';

let ws; //define it here so it's available in return function

function createEventChannel() 
    return eventChannel(emit => 
          function createWs() 
            //Subscribe to websocket
            ws = new WebSocket('ws://localhost:9000/rates');

            ws.onopen = () => 
                console.log("Opening Websocket");
            ;

            ws.onerror = error => 
                console.log("ERROR: ", error);
            ;

            ws.onmessage = e => 
                return emit(data: JSON.parse(e.data))
            ;

            ws.onclose = e => 
                if (e.code === 1005) 
                    console.log("WebSocket: closed");
                    // you probably want to end the channel in this case
                    emit(END);
                 else 
                    console.log('Socket is closed Unexpectedly. Reconnect will be attempted in 4 second.', e.reason);
                    setTimeout(() =>  
                        createWs();
                    , 4000);
                
            ;
        
        createWs();

        return () => 
            console.log("Closing Websocket");
            ws.close();
        ;
    );

【讨论】:

我面临的一个问题是您将如何使用此代码实现发送逻辑?我们需要一个回调或函数,一旦连接就返回套接字对象。 如果您只需要单例访问,一个有点老套的选择是导出一个返回内部wsgetWebsocketSingleton 函数。您可以在模块顶部设置ws = null;,在这种情况下在清理中设置,然后在其他地方运行ws.send 之前检查ws === null。如果使用这样的事件通道解决方案,不确定是否有更好的方法来公开 websocket。

以上是关于在 redux-Saga 中重新连接到 webSocket 服务器的标准方法?的主要内容,如果未能解决你的问题,请参考以下文章

使用 redux-saga 重定向反应路由器

连接到 WebSocket 时如何修复“Access-Control-Allow-Origin”错误?

neo4 WebSocket 连接到“ws://localhost:7687/”失败:通过代理服务器建立隧道失败

重启后重新连接到 Redis

Spring Boot - 重新启动后重新连接到数据库

在 log4j 中重新连接到数据库