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

use*_*596 17 ecmascript-6 reactjs 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
  }
};
Run Code Online (Sandbox Code Playgroud)

APP_LOADED调度动作被getTransactions调用时,流被打开并且当从服务器接收数据时调用onMessage侦听器,但是在调用yield put(transactions(txs))生成器时我没有运气调度动作foo.

谁能告诉我我做错了什么?

Yas*_*afi 32

Saga只能从另一个Saga(使用yield foo()yield call(foo))中调用.

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

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

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

redux-saga采用了不同的模式:Sagas 拉出了理想的赛事.作为回调,它们通常会进行一些处理.但是他们完全可以控制下一步该做什么:他们可能会选择再次拉同一个事件 - 模仿回调模型 - 但他们并没有被迫.他们可能选择拉另一个事件,启动另一个传奇以接收中继甚至终止他们的执行.即他们控制着自己的进步逻辑.所有事件源都可以解决未来事件的查询.

要集成外部推送源,我们需要将事件源从推送模型转换为拉模型; 即我们必须构建一个事件迭代器,我们可以从事件源中提取未来的事件

这是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
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

上面的函数返回一个对象,其中包含一个nextMessage可用于提取未来消息的方法.调用它将返回一个Promise,它将使用下一个传入消息解析.

拥有createSourceAPI功能.我们现在可以通过简单的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)
}
Run Code Online (Sandbox Code Playgroud)

您可以找到上述代码的实时运行演示.

上述方法的一个优点是,它使内部萨加斯代码完全声明性(仅使用声明性形式forkcall)

  • @ user5325596你的简化代码只要**仅用于**一个传奇**; 并且在查询下一条消息之前,该saga将始终等待下一条消息.否则,连续的`nextMessage`调用将继续覆盖延迟.所以只有最后一个延期将被解决 (4认同)
  • @ user5325596是的,`onmessage`回调将被安排在一个新的事件队列中.不会遗漏任何消息. (2认同)