Могу ли я использовать генераторы редукции saga es6 в качестве приемника onmessage для websockets или eventource?

Я пытаюсь получить саунд-редукцию, работающую с прослушивателем 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. Однако, чтобы ввести его, нам нужно перейти от 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)