When you use Redux-saga for everything (like I do), sooner or later you're going to run into a situation where you've got some external event or nested callback function that you need to integrate with a saga.
You'll quickly find that you can't yield put(action)
to your saga from inside a nested function. It's in the wrong function scope.
You also can't put(action)
from outside of the saga.
Remember that put()
doesn't really do anything by itself. It merely returns a description of your side-effect.
This is very much like how an action creator returns only a description of the action -- it is the reducer that is responsible for doing the real work.
Similarly, it's up to the redux-saga middleware to do the real work of your put()
side effect.
The Problem Pattern
function* saga() { // ... someObject.callback = function(event) { yield put({ type: ACTION, event }); // doesn't work }; // ...}
Can You Refactor?
If you control someObject
it may be possible to rewrite it as a generator function and can propagate yielded effects. (i.e. turn it into a saga too)
But sometimes you have no control over the code. For example, callbacks on built-in objects such as window, XMLHttpRequest, etc
You need a different approach.
Redux-Saga Event Channels
Redux-saga has something called an Event Channel that can bridge between the two function scopes.
After creating it, you can pass the channel around and use it to send events back to your saga. Your saga will take() from the channel just as if it were taking actions.
function* saga(someObject) { const channel = eventChannel(emitter => { someObject.on('data', function(data) { // Send the event to our saga emitter({ data }); }); someObject.on('end', function() { emitter(END); // Special construct to end the channel }); // Return an unsubscribe method return () => { // Perform any cleanup you need here someObject.end(); }; }); someObject.begin(); // Process events until operation completes while (true) { const { data } = yield take(channel); if (!data) { break; } // Handle the data... }}
See a concrete example in the follow-up post,
File Upload Progress with Redux-Saga
Further Reading
- Channels