Briefly about redux-saga channels

Good afternoon dear friends.


In this article I would like to describe the mechanism of the redux-saga channels as simple and concise as possible, with examples close to real cases, I hope it happened to me.


So, let's begin.


Problem watch-and-fork


Suppose we have an ordinary watch-and-fork model, of the following form:


import { take, fork } from'redux-saga/effects'function* watchRequest() {
  while (true) {
    const {payload} = yield take('INIT_REQUEST');
    // заметим, что вызов не блокирующийyield fork(makeRequest, payload);
  }
}
function* makeRequest(payload) { 
    // код саги
}

This approach is bad because when several events INIT_REQUESTfollowing each other are caught, several performances will be launched, respectively makeRequest. Which in turn can cause their “race”.


And here the channel mechanism comes to our rescue.


The channels have buffers, thereby helping to queue upcoming events (for example, INIT_REQUEST), and organize their sequential execution (for example, it makeRequestwill be executed sequentially several times).


Roughly speaking, the channels form a FIFO queue for sequential execution.


They are classified by event source:


  • channel- events are queued manually with put;
  • actionChannel - events are caught near the redux store;
  • eventChannel - external source of events, most often web socket;

So, let us briefly analyze each.


More about channel


Such channels usually solve the problem of communication between sagas. Used very rarely. For example, if you need to reconcile several requests that start at the same time.


channel([buffer])

It has a single argument buffer- the accumulating buffer (we will analyze the buffers in more detail below).


Read more about actionChannel


Most often used when it is necessary to respond to events from the redux store.


actionChannel(pattern, [buffer])

Takes two arguments:


  • pattern- pattern of the required events, as well as take;
  • buffer - accumulating buffer;

Brief example of use:


import { take, actionChannel, call } from'redux-saga/effects'function* watchRequest() {
  const requestChannel = yield actionChannel('INIT_REQUEST')
  while (true) {
    const {payload} = yield take(requestChannel);
    // заметим что вызов теперь блокирующийyield call(makeRequest, payload);
  }
}
function* makeRequest(payload) {
    // код саги
}

More on eventChannel


Usually through him solve the problem of communication through the web socket.


eventChannel(subscribe, [buffer], [matcher])

Takes three arguments:


  • subscribe- function initializing external source of events (in the example below, setTimeout). The arguments callback, called emitter, which will be called when you need to send data to the channel from the source. Return must function unsubscribe;
  • buffer - accumulating buffer;
  • matcher - function to filter incoming messages.

Brief example of use:


import { eventChannel, END } from'redux-saga'import { take, put, call } from'redux-saga/effects'functioninitSocketChannel(query) {
  return eventChannel(emitter => {
      // эмулируем получение данных через web socketconst handshakeTimeoutId = setTimeout(() => {
          emitter('handshake - ok');
      }, 100);
      const messageTimeoutId = setTimeout(() => {
          emitter('message');
      }, 500);
      const endTimeoutId = setTimeout(() => {
          emitter(END);
      }, 1000);
      // функция отписки от каналаreturn() => {
        clearTimeout(handshakeTimeoutId);
        clearTimeout(messageTimeoutId);
        clearTimeout(endTimeoutId);
      }
    }
  )
}
exportfunction* saga() {
  const chan = yield call(initSocketChannel, query)
  try {    
    while (true) {
      const message = yield take(chan);
      // при возвращении каналом END сработает обычный brakeconsole.log(`socket : ${message}`)
    }
  } finally {
    console.log('socket terminated')
  }
}

Surely you have noticed the presence of a constant END- this is an action that means the end of communication with the channel.


In the source code, redux-saga is represented as follows:


var CHANNEL_END_TYPE = '@@redux-saga/CHANNEL_END';
var END = { type: CHANNEL_END_TYPE };
var isEnd = functionisEnd(a) {
  return a && a.type === CHANNEL_END_TYPE;
};

and in the source code eventChannelwe see the following script


functioneventChannel(subscribe) {
    …
    if (isEnd(input)) {
        close();
        return;
    }
    ...
}

What is a buffer?


It is worthy of attention, since each channel has a base buffer, with the help of it a queue is formed for processing.


Buffer creation example:


import { buffers } from'redux-saga'const buffer = buffers.sliding(5);

buffers - This is the instance of the factory to create buffers with different strategies.


Only 5 strategies, they correspond to the methods:


  • buffers.none() - no buffering, new messages will be lost if there are no pending participants;
  • buffers.fixed(limit)- new messages will be buffered to the limit. An overflow error will result in an error (exeption). The default limit is 10;
  • buffers.expanding(initialSize) - like fixed, but overflow will cause the buffer to expand dynamically;
  • buffers.dropping(limit) - the same as fixed, but the overflow will silently discard messages;
  • buffers.sliding(limit) Is the same as fixed, but overflow will add a new message to the end and delete the oldest message in the buffer.

Instead of


So, we have disassembled why the mechanism of channels was invented, and what practical tasks are used.


Hopefully, after reading, a general idea is formed and the world has become a bit simpler.


Also popular now: