Могу ли я использовать генераторы es6-системы Sasex-Saga в качестве прослушивателя сообщений для веб-сокетов или источника событий?
Я пытаюсь заставить Саудовскую Сагу работать с onmessage
слушатель. Я не знаю, почему то, что у меня есть, не работает.
У меня есть следующие настройки.
// sagas.js
import { take, put } from 'redux-saga';
import {transactions} from "./actions";
function* foo (txs) {
console.log("yielding"); // appears in console
yield put(transactions(txs)); // action *is not* dispatched
console.log("yielded"); //appears in console
}
const onMessage = (event) => {
const txs = JSON.parse(event.data);
const iter = foo(txs);
iter.next(); // do I really need to do this?
};
function* getTransactions() {
while(yield take('APP_LOADED')) {
const stream = new EventSource(eventSourceUrl);
stream.onopen = onOpen;
stream.onmessage = onMessage;
stream.onerror = onError;
// this is just testing that `yield put` works
yield put(transactions([{baz : 42}])); //this action *is* dispatched
}
};
Когда APP_LOADED
действие отправлено getTransactions
вызывается, поток открывается и слушатель onMessage вызывается при получении данных с сервера, но мне не везет в отправке действия при вызове yield put(transactions(txs))
в генераторе foo
,
Может кто-нибудь сказать мне, что я делаю не так?
1 ответ
Сага может быть вызвана только из другой Саги (используя yield foo()
или же yield call(foo)
)
В вашем примере foo
Сага вызывается изнутри нормальной функцией (onMessage
callback), поэтому он просто вернет объект итератора. Выдавая итератор (или вызов генератора) из Saga, мы позволяем промежуточному программному обеспечению redux-saga перехватывать этот вызов и запускать итератор, чтобы разрешить все полученные эффекты. Но в вашем коде stream.onmessage = onMessage
просто сделайте простое назначение, чтобы промежуточное ПО ничего не заметило.
Что касается основного вопроса. Sagas обычно принимает события из магазина Redux. Ты можешь использовать runSaga
подключить сагу к пользовательскому источнику ввода / вывода, но не так просто применить это к описанному выше варианту использования. Поэтому я предложу другую альтернативу, используя просто call
эффект. Тем не менее, чтобы представить его, нам нужно перейти от толкающей перспективы событий к перспективной.
Традиционный способ обработки событий состоит в том, чтобы зарегистрировать прослушиватель некоторых событий в каком-либо источнике событий Как назначение onMessage
обратный звонок в stream.onmessage
в приведенном выше примере. Каждое событие происходит в обратном вызове слушателя. Источник события находится под полным контролем.
Редукс-сага принимает другую модель: саги тянут желаемое событие. Как обратные вызовы, они обычно выполняют некоторую обработку. Но они имеют полный контроль над тем, что делать дальше: они могут выбрать повторное выполнение того же события - что имитирует модель обратного вызова - но они не вынуждены это делать. Они могут решить провести другое Событие, начать другую Сагу, чтобы взять эстафету, или даже прекратить их выполнение. то есть они контролируют свою собственную логику прогресса. Все, что может сделать источник событий - это разрешить запросы для будущих событий.
Чтобы интегрировать внешние источники push, нам нужно будет перенести источник событий из модели push в модель pull; т.е. нам нужно будет создать итератор событий, из которого мы сможем извлечь будущие события из источника событий
Вот пример получения onmessage
итератор из EventSource
function createSource(url) {
const source = new EventSource(url)
let deferred
source.onmessage = event => {
if(deferred) {
deferred.resolve(JSON.parse(event.data))
deferred = null
}
}
return {
nextMessage() {
if(!deferred) {
deferred = {}
deferred.promise =
new Promise(resolve => deferred.resolve = resolve)
}
return deferred.promise
}
}
}
Вышеуказанная функция возвращает объект с nextMessage
метод, который мы можем использовать, чтобы вытащить будущие сообщения. Вызов его вернет Обещание, которое разрешится со следующим входящим сообщением.
Имея createSource
API-функция. Теперь мы можем использовать его простым call
эффект
function* watchMessages(msgSource) {
let txs = yield call(msgSource.nextMessage)
while(txs) {
yield put(transactions(txs))
txs = yield call(msgSource.nextMessage)
}
}
function* getTransactionsOnLoad() {
yield take('APP_LOADED')
const msgSource = yield call(createSource, '/myurl')
yield fork(watchMessages, msgSource)
}
Вы можете найти демо-версию приведенного выше кода.
Преимущество вышеупомянутого подхода состоит в том, что он сохраняет код внутри Sagas полностью декларативным (используя только декларативные формы fork
а также call
)