question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Subscribing to websocket events

See original GitHub issue

Hi. I would really appreciate some clarification om how to handle my case. Sorry for a bit lengthy explanation, but I’d like to describe every bit involved.

I have several sagas:

  1. XHR API middleware that takes the API request action, calls the API, waits for the promise to resolve, etc. Pretty standard and I have no issues with it. It uses an isolated “service” module to make the actual XHR calls.
  2. Websocket middleware that has 3 different sagas: connect, subscribe, emit. It uses an isolated service (which essentially wraps the socket.io-client lib). The connect method returns a promise (and works OK). The emit method is synchronous, works fine as well. The subscribe method is the one that baffles me. The issue here is that events from the socket can happen multiple times, so I cannot promisify this method. See below for how I use it.
  3. The actual “meat” saga that has to do the following: take the XHR success event for a specific resource (chat rooms list), subscribe to the websocket events for the new messages, and emit the subscription websocket event (that will actually put the current client into the listeners list for the given char rooms on the server).

Now here’s how the latter saga looks:

export default function* myRooms(getState) {
  while (true) {
    yield take(MY_ROOMS_SUCCESS) // works OK

    yield put(subscribeSocket({ // it's sent and taken just fine in the other saga
      event: 'newMessage', // the name of the socket.io event to subscribe to
      actionType: MY_ROOMS_NEW_MESSAGE // the name of the action to dispatch
    }))

    const state = getState()
    const roomIds = ... // get the IDs of the chat rooms
    yield put(emitSocket({
      event: 'subscribe', // the name of the socket.io event to emit
      payload: roomIds
    }))
  }
}

Now for the socket subscription saga:

function* subscribeSocket(getState) {
  while (true) {
    const nextAction = yield take(SUBSCRIBE_SOCKET)
    const subscribeConfig = nextAction.payload
    const { event, actionType } = subscribeConfig
    const callback = (payload) => put({ type: actionType, payload }) // <-- here
    const unsibscribe = socket.subscribe(event, callback)
  }
}

So the subscription saga gets the socket event name to listen to, and the action type name to dispatch together with the event payload. I can tell from the debugging that the callback is actually executed. As far as I understand the problem here is the improper put usage.

The question is: what is the proper usages of sagas in this case? I want to dispatch the actionType every time the callback is called.

Thanks in advance!

Issue Analytics

  • State:closed
  • Created 8 years ago
  • Reactions:2
  • Comments:16 (6 by maintainers)

github_iconTop GitHub Comments

16reactions
sslotskycommented, Jul 2, 2016

For what it’s worth, I got around to testing my code and it was almost correct. Here’s a working version with eventChannel

import { eventChannel, takeEvery } from 'redux-saga'
import { take, call, put, fork, cancel, cancelled } from 'redux-saga/effects'
import socket, { channel } from './socket'
import * as actionTypes from './constants'

function socketEmitter(subject) {
  return eventChannel(emitter => {
    socket.emit('track', subject)
    const newChannel = channel(subject)

    newChannel.on('tweets', tweets => {
      emitter(tweets)
    })

    return () => {
      newChannel.removeAllListeners('tweets')
      socket.emit('untrack', subject)
    }
  })
}

function* listen(subject) {
  const chan = yield call(socketEmitter, subject)
  try {
    while (true) {
      let tweets = yield take(chan)
      yield put({
        type: actionTypes.TWEETS_RECEIVED,
        subject,
        tweets,
        read: false
      })
    }
  } finally {
    if (yield cancelled())
      chan.close()
  }
}

function* subscribe(action) {
  while (true) {
    const listenTask = yield fork(listen, action.subject)
    const unsubscribed = yield take(actionTypes.STOP_TRACKING)
    if (action.subject == unsubscribed.subject)
      yield cancel(listenTask) 
  }
}

function* track() {
  yield* takeEvery(actionTypes.TRACK_SUBJECT, subscribe)
}

export default track
11reactions
yelouaficommented, Jan 23, 2016

@emirotin I think this maybe related to this SO question. The main point is

To integrate external push sources, we’ll need to transpose the Event Source from the push model into the pull model; i.e. we’ll have to build an event iterator from which we can pull the future events from the event source

It joins somewhat your solution, but factor out the concept into a separate function. So the saga can be easily tested. You can find a jsbin live demo here https://jsbin.com/wumuri/edit?js,console

// Event iterator for socket events
function socketEventIterator(event) {
  let deferred
  socket.subscribe(event, payload => {
    if(deferred) {
      deferred.resolve(payload)
      deferred = null 
    }
  }

  return {
    nextEvent() {
      if(!deferred) {
        deferred = {}
        deferred.promise = 
          new Promise(resolve => deferred.resolve = resolve)
      }
      return deferred.promise
    }
  }
}

function* listenToSocket(event, actionType) {
  const { nextEvent } = yield call(socketEventIterator, event)
  while (true) {
    const payload = yield call(nextEvent)
    yield put({ type: actionType, payload })
  }
}

function* subscribeSocket(getState) {
  while (true) {
    const nextAction = yield take(SUBSCRIBE_SOCKET)
    const subscribeConfig = nextAction.payload
    const { event, actionType } = subscribeConfig
    yield fork(listenToSocket, event, actionType)
  }
}
Read more comments on GitHub >

github_iconTop Results From Across the Web

Using WebSocket subscriptions without queues
In this post, you can learn about the WebSocket Protocol and how to use WebSocket subscriptions without queues.
Read more >
how to capture subscribe event in my webSocket server with ...
It works, but captures all possible events, I don't think it is a good practice. So, my question can be rephrased: how to...
Read more >
Subscribing to events via Websocket - Tendermint Core
Tendermint emits different events, which you can subscribe to via Websocket (opens new window). This can be useful for third-party applications (for analysis) ......
Read more >
Handling WebSocket Events | Twitch Developers
To discover the events that a WebSocket session subscribes to, use the Get EventSub Subscriptions endpoint. For details, see Getting the list of...
Read more >
WebSocket: message event - Web APIs | MDN
The message event is fired when data is received through a WebSocket.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Hashnode Post

No results found