我试图让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
时调度操作。
谁能告诉我我做错了什么?
答案 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中的代码保持完全声明(仅使用声明性表单fork
和call
)