【问题标题】:Standard way of reconnecting to webSocket server in redux-Saga?在 redux-Saga 中重新连接到 webSocket 服务器的标准方法?
【发布时间】: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 上断开连接,一段时间后我们想再次连接怎么办?

标签: reactjs redux websocket redux-saga


【解决方案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。
猜你喜欢
  • 2021-09-16
  • 1970-01-01
  • 1970-01-01
  • 2021-10-18
  • 1970-01-01
  • 2020-07-04
  • 2023-02-22
  • 2022-08-24
  • 1970-01-01
相关资源
最近更新 更多