Вызовите редукционное действие в рамках redux-саги в обратном вызове websocket (stomp + sock js) - PullRequest
3 голосов
/ 06 марта 2020

Я использую redux и redux-saga в моем проекте. Прямо сейчас при использовании WebSocket у меня проблема с вызовом действия FETCH_SUCCESS redux внутри обратного вызова ответа сокета. Я также пытался сделать колбэк генератором, но он тоже не работал.

function* websocketSaga() {
  const socket = new SockJS(`${CONFIG.API_URL}/ws`);
  const stomp = Stomp.over(socket);
  const token = yield select(selectToken);
  stomp.connect(
    {
      Authorization: `Bearer ${token}`,
    },
    frame => {
      stomp.subscribe('/queue/data', message => {
        const response = JSON.parse(message.body);
        console.log(response); // here is the proper response, it works
        put({
          type: FETCH_SUCCESS, // here the FETCH_SUCCESS action is not called
          payload: response.dataResponse,
        });
      });
      ...
      ....
    }
  );
}

Или, может быть, этот WebSocket должен быть реализован совершенно по-другому в redux-saga?

Ответы [ 3 ]

4 голосов
/ 08 марта 2020

Вы не сможете использовать yield put внутри функции обратного вызова. Stomp js ничего не знает о сагах, поэтому он не знает, что он должен делать, когда ему предоставляется функция генератора.

Самый простой подход, хотя и не обязательно лучший, заключается в go непосредственно в сохраните в обратном вызове избыточное хранилище и отправьте действие, не задействуя избыточную сагу. Например:

import store from 'wherever you setup your store'

// ...
stomp.subscribe('/queue/data', message => {
  const response = JSON.parse(message.body);
  store.dispatch({
    type: FETCH_SUCCESS,
    payload: response.dataResponse,
  });
});

Если вы хотите использовать более редуцированный сага-й подход, я бы рекомендовал заключить подписку в канал событий . Каналы событий используют API на основе обратного вызова и превращают его во что-то, с чем вы можете взаимодействовать, используя эффекты redux-saga, такие как take

Вот как вы можете создать канал событий:

import { eventChannel } from 'redux-saga';

function createChannel(token) {
  return eventChannel(emitter => {
    const socket = new SockJS(`${CONFIG.API_URL}/ws`);
    const stomp = Stomp.over(socket);
    stomp.connect(
      {
        Authorization: `Bearer ${token}`,
      },
      frame => {
        stomp.subscribe('/queue/data', message => {
          const response = JSON.parse(message.body);
          emitter(response); // This is the value which will be made available to your saga
        });
      }
    );

    // Returning a cleanup function, to be called if the saga completes or is cancelled
    return () => stomp.disconnect();
  });
}

И тогда ты будешь использовать это так:

function* websocketSaga() {
  const token = yield select(selectToken);
  const channel = createChannel(token);
  while (true) {
    const response = yield take(channel);
    yield put({
      type: FETCH_SUCCESS,
      payload: response.dataResponse,
    });
  }
}
1 голос
/ 14 марта 2020

Я дам вам другой способ управления этим: создайте компонент, подключенный к redux, где вы будете обрабатывать подписку WS. Этот компонент не будет ничего визуализировать в пользовательском интерфейсе, но будет полезен для обработки взаимодействий с избыточным хранилищем.

Основная идея заключается в том, что не помещайте все в redux-saga, попробуйте разбить его на несколько частей, чтобы сделать его проще в обслуживании.

const socket = new SockJS(`${CONFIG.API_URL}/ws`);

function WSConnection(props) {
  const {token, fetchDone} = props;
  const [stomp, setStomp] = React.useState();

  const onMessage = React.useCallback(message => {
    const response = JSON.parse(message.body);
    fetchDone(response.dataResponse);
  }, [fetchDone]);

  const onConnect = React.useCallback(frame => {
    const subscription = stomp.subscribe('/queue/data', onMessage);
    // cleanup subscription
    return () => subscription.unsubscribe();
  }, [stomp, onMessage]);

  const onError = React.useCallback(error => {
    // some error happened, handle it here
  }, []);

  React.useEffect(() => {
    const header = {Authorization: `Bearer ${token}`};
    stomp.connect(header, onConnect, onError);
    // cleanup function
    return () => stomp.disconnect();
  }, [stomp])

  React.useEffect(() => {
    setStomp(Stomp.over(socket));
  }, []);

  return null;
}

const mapStateToProps = state => ({
... // whatever you need from redux store
});

const mapDispatchToProps = dispatch => ({
... // whatever actions you need to dispatch
});

export default connect(mapStateToProps, mapDispatchToProps)(WSConnection);

Вы также можете сделать еще один шаг и извлечь stomp logi c в другой файл и повторно использовать его там, где он вам понадобится.

Это не так поместить все в redux-saga, но это хорошая альтернатива для обработки WS-соединений внутри компонентов, подключенных к redux (и это легче понять людям, которые не совсем знакомы с redux-saga и channel et c).

1 голос
/ 11 марта 2020

Promise должно идеально подходить. Просто оберните связанный с обратным вызовом код в обещание и resolve в функцию обратного вызова. После этого используйте yield, чтобы получить данные из обещания. Я изменил ваш код с Promise ниже.

function* websocketSaga() {
  const socket = new SockJS(`${CONFIG.API_URL}/ws`);
  const stomp = Stomp.over(socket);
  const token = yield select(selectToken);

  const p = new Promise((resolve, reject) => {
    stomp.connect(
      {
        Authorization: `Bearer ${token}`,
      },
      frame => {
        stomp.subscribe('/queue/data', message => {
          const response = JSON.parse(message.body);
          console.log(response); // here is the proper response, it works
          resolve(response); // here resolve the promise, or reject if any error
        });
        ...
        ....
      }
    );
  });

  try {
    const response = yield p;  // here you will get the resolved data
    yield put({
      type: FETCH_SUCCESS, // here the FETCH_SUCCESS action is not called
      payload: response.dataResponse,
    });
  } catch (ex) {
    // handle error here, with rejected value
  }

}
...