Могу ли я использовать генераторы es6 redux-saga в качестве прослушивателя onmessage для websockets или eventsource?

Я пытаюсь заставить redux-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 Saga вызывается изнутри нормальной функции (onMessage callback), поэтому он просто вернет объект итератора. Предоставляя итератор (или вызов генератора) из Saga, мы позволяем промежуточному по redux-saga Конст и выполнить итератор для разрешения всех полученных эффектов. Но в коде stream.onmessage = onMessage просто сделайте простое назначение, чтобы промежуточное ПО ничего не заметило.

что касается главного вопроса. Sagas обычно принимает события из магазина Redux. Вы можете использовать runSaga для подключения саги к пользовательскому источнику ввода / вывода, но это не будет тривиальным, чтобы применить это к приведенному выше случаю использования. Поэтому я предложу другую альтернативу, используя просто call эффект. Однако, чтобы ввести его, нам придется перейти от пуш перспектива событий, к тянуть точки зрения.

традиционный способ обработки событий-зарегистрировать прослушиватель событий в некотором источнике событий. Например, назначение onMessage обратный вызов stream.onmessage в примере выше. Каждое событие имеет значение толкнул для обратного вызова слушателя. Источник событий находится под полным контролем.

redux-saga принимает другую модель: Sagas тянуть желаемое событие. Как обратные вызовы, они обычно выполняют некоторую обработку. Но они иметь полный контроль над тем, что делать дальше: они могут выбрать, чтобы вытащить то же самое событие снова-что имитирует модель обратного вызова - но они не вынуждены. Они могут выбрать, чтобы вытащить другое событие, начать другую сагу, чтобы взять эстафету или даже прекратить их выполнение. они контролируют собственную логику прогрессии. Все, что может сделать источник событий, это разрешить запросы для будущих событий.

чтобы интегрировать внешние push-источники, нам нужно транспонировать источник событий из push модель в модель тяги; т. е. нам придется построить событие итератор из которого мы можем вытащить будущие события из источника событий

вот пример получения 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)
}

вы можете найти live running demo вышеуказанного кодекса.

преимуществом вышеуказанного подхода является то, что он сохраняет код внутри саг полностью декларативным (используя только декларативные формы fork и call)