Ответ 1
Сага может быть вызвана только изнутри другой Саги (используя yield foo()
или yield call(foo)
).
В вашем примере сага foo
вызывается изнутри нормальной функции (onMessage
callback), поэтому она просто вернет объект итератора. Получив итератор (или вызов генератора) из Saga, мы разрешаем промежуточному программному обеспечению redux-saga перехватить этот вызов и запустить итератор, чтобы разрешить все полученные эффекты. Но в вашем коде stream.onmessage = onMessage
просто выполните простую привязку, чтобы промежуточное ПО не заметило ничего.
Что касается основного вопроса. Sagas обычно принимает события из магазина Redux. Вы можете использовать runSaga
для подключения саги к пользовательскому источнику ввода/вывода, но не обязательно тривиально применять это к вышеуказанному варианту использования. Поэтому я предлагаю другую альтернативу, используя просто эффект call
. Однако, чтобы ввести его, нам нужно перейти от push к перспективам событий, к перспективе тянуть.
Традиционный способ обработки событий - зарегистрировать прослушиватель событий на каком-либо источнике событий. Подобно назначению обратного вызова onMessage
на stream.onmessage
в приведенном выше примере. Каждое событие события помещается в обратный вызов слушателя. Источник события находится в режиме полного контроля.
redux-saga использует другую модель: Sagas тянет желаемое событие. Как обратные вызовы, они обычно выполняют некоторую обработку. Но у них есть полный контроль над тем, что делать дальше: они могут снова выбрать одно и то же событие, которое имитирует модель обратного вызова, но они не вынуждены. Они могут выбрать другое событие, запустить другую сагу, чтобы принять реле или даже прекратить исполнение. то есть они контролируют свою собственную логику прогрессирования. Весь источник событий может сделать это, чтобы разрешить запросы для будущих событий.
Чтобы интегрировать внешние источники 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
)