我可以使用redux-saga的es6生成器作为webockets或eventsource的onmessage监听器吗?

时间:2016-01-18 16:48:19

标签: reactjs ecmascript-6 redux redux-saga

我试图让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 个答案:

答案 0 :(得分:32)

只能从另一个Saga内部调用Saga(使用yield foo()yield call(foo))。

在您的示例中,foo Saga是从普通函数(onMessage回调)内部调用的,因此它只返回迭代器对象。通过从Saga中产生迭代器(或对生成器的调用),我们允许redux-saga中间件拦截调用并运行迭代器以解决所有问题产生了效果。但是在你的代码中,stream.onmessage = onMessage只是做了一个简单的分配,所以中间件不会注意到任何东西。

至于主要问题。 Sagas通常从Redux商店获取事件。您可以使用runSaga将saga连接到自定义输入/输出源,但将其应用于上述用例并不容易。所以我将仅使用call效果提出另一种选择。但是,为了引入它,我们必须从事件的推送视角转向视角。

处理事件的传统方法是在某些事件源上注册一些事件侦听器。就像在上面的示例中将onMessage回调分配给stream.onmessage一样。每个事件发生都推送到侦听器回调。事件源完全控制。

redux-saga采用了不同的模型:Sagas 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方法的对象,我们可以用它来提取未来的消息。调用它将返回一个Promise,它将使用下一个传入消息解析。

拥有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

上述方法的一个优点是它可以使Sagas中的代码保持完全声明(仅使用声明性表单forkcall