Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Call redux action within redux-saga inside websocket callback (stomp + sockjs)

I am using redux and redux-saga in my project. Right now using WebSocket I have a problem calling a FETCH_SUCCESS redux action inside a callback of socket response. I tried making the callback a generator as well but didn't work as well.

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,
        });
      });
      ...
      ....
    }
  );
}

Or maybe this WebSocket should be implemented in a completely different way in redux-saga?

like image 699
heisenberg7584 Avatar asked Dec 22 '22 19:12

heisenberg7584


2 Answers

You won't be able to use yield put inside a callback function. Stompjs knows nothing about sagas, so it doesn't know what it's supposed to do when given a generator function.

The simplest approach, though not necessarily the best, is to go directly to the redux store in the callback, and dispatch the action without involving redux-saga. For example:

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,
  });
});

If you'd like to use a more redux-saga-y approach, I would recommend wrapping the subscription in an event channel. Event channels take a callback-based API and turn it into something that you can interact with using redux-saga's effects such as take

Here's how you might create the event channel:

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();
  });
}

And then you'd use it like this:

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,
    });
  }
}
like image 160
Nicholas Tower Avatar answered Jan 06 '23 01:01

Nicholas Tower


Promise should be the perfect fit. Just wrap the callback related code in a promise and resolve it in the callback function. After that use the yield to get the data from the promise. I have modified your code with the Promise below.

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
  }

}
like image 23
RaviNila Avatar answered Jan 06 '23 03:01

RaviNila