Могу ли я использовать генераторы 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)

Другие вопросы по тегам