Idea: Sagas 'returning' promises from middleware (to support Server Side Rendering)
See original GitHub issueLast night I was working with sagas, doing standard-fare authentication flow. However, I use server rendering, and sagas seem to be lacking for this.
In my existing apps, I do something like dispatch(login(email, password)).then(/*...*/)
. I then proceed to render the route and send a response. Of course, with sagas it’s not as simple.
I have read #13 and runSagas
is a good option to have. However, what if we consider an effect that alters the response of dispatch
on certain actions?
Let’s call it takeDefer
for now. A better name would be nice if this idea is viable
function doLogin(email, password) {
const promise = dispatch({ type: 'LOGIN', payload: { email, password } });
promise.then(/*...*/).catch(/*...*/);
}
function * login() {
while (true) {
// Right now, dispatch(LOGIN) would return a POJO
// Once the generator yields `takeDefer` to the middleware, it latches
// on and alters the return value of `dispatch(LOGIN)`
const task = yield takeDefer('LOGIN');
// At this point, subsequent dispatch(LOGIN) would not return a promise
const { username, password } = task.action.payload;
const authTask = yield fork(authorize, username, password);
const { fail } = yield race({
success: take('LOGIN_SUCCESS'),
fail: take('LOGIN_FAIL'),
})
if (fail) {
task.reject('Login has failed')
} else {
task.resolve('Login complete!')
}
yield take('LOGOUT');
}
}
If there were multiple takeDefer('LOGIN')
effects concurrently waiting, the promise returned from the middleware would be Promise.all(...)
. The array of promises could be made available as well.
I believe this is possible, and would love to give a whack at a PR. However, does this sound sane? Does this kind of usage of sagas break their intent?
From a practical standpoint, being able to leverage promises like this could be very useful for server rendering–I could reuse the same client-saga on the server, and jump out of it once I have what is needed. Also, it would help with CPS methods such as onEnter
in React Router.
Issue Analytics
- State:
- Created 8 years ago
- Comments:18 (17 by maintainers)
Top GitHub Comments
@yelouafi at first blush, I like this approach. I’ll try and provide some more thoughtful feedback soon.
Thanks for putting so much effort into this! If you happen to be going to React Europe, I’ll buy you a beer!
@quicksnap @pavelkornev I’ve a proposed solution here. In https://github.com/yelouafi/redux-saga/issues/78#issuecomment-203886961 I propose that a parent is terminated only when all attached forked tasks terminate. If we combine this with a special
END
action that I intend to introduce. We can have soemthing like thisEND
is a special action, it notifies all takers that no more actions will be dispatched. And since a saga won’t terminate until all forked tasks terminate, we can use thedone
promise of the root saga to determine when all forked tasks are completed. Similarly the root saga will abort on the first error encountered by a forked task so we can use this to send an error to the client.W’ll be rendering twice of course on the server, but The main advantage here is that the same saga code would run on the server and client