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, using examples close to real cases, I hope it happened to me.
So, let's begin.
Suppose that we have the usual 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 catching several INIT_REQUEST
events INIT_REQUEST
one after another, several makeRequest
executions will be launched, respectively. Which in turn can cause their “race”.
And here the channel mechanism comes to our rescue.
The channels have buffers, thereby helping to queue INIT_REQUEST
events (for example, INIT_REQUEST
), and organize their sequential execution (for example, makeRequest
will makeRequest
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 using put
;actionChannel
- events are caught near the redux store;eventChannel
- external source of events, most often web socket;So, let's briefly analyze each.
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 buffer
argument - the accumulating buffer (we'll take a closer look at the buffers below).
Most often used when it is necessary to respond to events from the redux store.
actionChannel(pattern, [buffer])
Takes two arguments:
pattern
- the pattern of the required events, as well as take
;buffer
- storage 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) { // }
Usually through him solve the problem of communication through the web socket.
eventChannel(subscribe, [buffer], [matcher])
Takes three arguments:
subscribe
is a function that initializes an 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 should function unsubscribe;buffer
- storage buffer;matcher
is a function for filtering incoming messages.Brief example of use:
import { eventChannel, END } from 'redux-saga' import { take, put, call } from 'redux-saga/effects' function initSocketChannel(query) { return eventChannel(emitter => { // web socket const 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); } } ) } export function* saga() { const chan = yield call(initSocketChannel, query) try { while (true) { const message = yield take(chan); // END brake console.log(`socket : ${message}`) } } finally { console.log('socket terminated') } }
Surely you have noticed the presence of the 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 = function isEnd(a) { return a && a.type === CHANNEL_END_TYPE; };
and in the eventChannel
source code eventChannel
see the following script
function eventChannel(subscribe) { … if (isEnd(input)) { close(); return; } ... }
It deserves attention, since each channel has a base buffer, and with it, a queue for processing is formed.
Buffer creation example:
import { buffers } from 'redux-saga' const buffer = buffers.sliding(5);
buffers
are instances of buffer factories 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 result in the buffer expanding 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.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.
Source: https://habr.com/ru/post/432070/
All Articles