一个 Saga 只能从另一个 Saga 内部调用(使用yield foo()
or yield call(foo)
) .
在你的例子中,foo
Saga 是从普通函数内部调用的(onMessage
回调),因此它只会返回迭代器对象。通过从 Saga 生成迭代器(或对生成器的调用),我们允许 redux-saga 中间件截距那个电话和run迭代器以解析所有产生的效果。但在你的代码中,stream.onmessage = onMessage
只需做一个简单的分配,这样中间件就不会注意到任何事情。
至于主要问题。 Sagas 通常从 Redux 存储中获取事件。您可以使用runSaga
将传奇连接到自定义输入/输出源,但将其应用于上述用例并不简单。所以我会提出另一种选择,简单地使用call
影响。然而,为了引入它,我们必须从push事件的视角,pull看法。
处理事件的传统方法是在某个事件源上注册一些事件侦听器。就像分配onMessage
回调到stream.onmessage
在上面的例子中。每个事件的发生是pushed到侦听器回调。事件源完全受控。
redux-saga 采用不同的模型:Sagaspull所需的事件。作为回调,它们通常会进行一些处理。但他们对下一步要做什么有完全的控制权:他们可能会选择再次拉取相同的事件——这模仿了回调模型——但他们并不是被迫这样做。他们可能选择拉动另一个事件,启动另一个 Saga 来接力,甚至终止他们的执行。也就是说,他们控制着自己的进展逻辑。事件源所能做的就是解决对未来事件的查询。
为了集成外部推送源,我们需要将事件源从推送模型转置为拉取模型;即我们必须建立一个事件迭代器我们可以从事件源中提取未来事件
这是一个推导的例子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,该 Promise 将通过下一条传入消息进行解析。
拥有createSource
API函数。我们现在可以通过简单的方式使用它call
effect
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)
}
你可以找到一个现场运行演示 https://jsbin.com/wumuri/edit?js,console上面的代码。
上述方法的一个优点是它使 Sagas 中的代码保持完全声明性(仅使用声明性形式)fork
and call
)