question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Idea: Sagas 'returning' promises from middleware (to support Server Side Rendering)

See original GitHub issue

Last 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:closed
  • Created 8 years ago
  • Comments:18 (17 by maintainers)

github_iconTop GitHub Comments

1reaction
quicksnapcommented, Mar 31, 2016

@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!

1reaction
yelouaficommented, Mar 31, 2016

@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 this

function* watcher() {
  let action
  while((action = take('ACTION') !== END) {
    yield fork(worker, action.payload)
  }
}

function* rootSaga() {
  yield [
    fork(watcher),
    fork(anotherWatcher)
  ]
}


// on the server rendering code, inside match

const sagaRunner = createSagaMiddleware() // no saga is passed here
const store = createStore(reducer, applyMiddleware(sagaRunner))
const rootTask = sagaRunner.run(rootSaga)

renderToString(RootComponent)
// will break the while loop on watchers
store.dispatch(END)

// rootTask will resolve only when all tasks in the program are terminated
rootTask.done.then(/* render and send state + html to the client */)

END 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 the done 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

Read more comments on GitHub >

github_iconTop Results From Across the Web

Middleware for the Async Flow in Redux - Bits and Pieces
The promise middleware returns a promise to the caller in order to wait for the async operation to be completed. This is useful...
Read more >
Chapter 6. Handling complex side effects - Redux in Action
In chapter 4, you handled simple side effects within action creators by ... Sagas return effects, which are instructions for the saga middleware...
Read more >
The power of Redux-Saga - Medium
Ability to dispatch another action from within the middleware. This will allow us the flexibility to trigger other side effects that have ...
Read more >
Why do we need middleware for async flow in Redux?
What is wrong with this approach? Why would I want to use Redux Thunk or Redux Promise, as the documentation suggests?
Read more >
Async Code with Middleware (Redux-Saga) - YouTube
In this third part, we'll look into what middlewares are and how to use them to make asynchronous API calls in our redux...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found