Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Best approach to keep pulling GET requests from backend server #127

Open
hadim opened this issue Oct 4, 2018 · 10 comments
Open

Best approach to keep pulling GET requests from backend server #127

hadim opened this issue Oct 4, 2018 · 10 comments
Labels

Comments

@hadim
Copy link

hadim commented Oct 4, 2018

Hi,

This lib looks super nice and I am tempting to integrate it into a project. I would like to know if you have an opinion on the best approach to keep pulling GET requests from backend server.

When the user login I need to start this infinite loop to keep getting data from the backend server at regular interval and cancel the loop when the user logout.

Then according to the type of message received by the backend, I would need to trigger some actions/logic.

@hadim
Copy link
Author

hadim commented Oct 4, 2018

Here is an idea, make a getLogic() that receives a GET request then do two things:

  • Dispatch according to the type of message.
  • Call getLogic() again.

@skbolton
Copy link

skbolton commented Oct 6, 2018

I can lump my question in with this too since I think mine is similar. Lets say I have a page that needs to poll the server for updates. I was thinking on mount the component would dispatch an action saying "subcribe me to updates on the server". Then on unmount the component would dispatch the cancel action to stop it from happening.
Maybe along the lines of this? Sorry I am sure I am missing some details still an rxjs noob.

const pollingLogic = createLogic({
  type: 'subscribe.me',
  cancelType: 'unsubscribe.me',
  process ({ http }, dispatch, _done) {
    return intervalObservable(30000).pipe(
      flatMap(() => http.get('/server'),
     // how do I dispatch the action at this point to update component based on server data? 
     tap(data => dispatch({ type: 'udpatedpolling', payload: data }))
  }
})

Mostly I am just confused if its weird my middleware would never call done. Or if I can even keep dispatching the events?

@jeffbski
Copy link
Owner

jeffbski commented Oct 7, 2018

Thanks for the question @hadim and @skbolton. @skbolton you are on the right track here. There are many ways to approach it, but you tackled it the way I would.

If you just need to do the same dispatch on every response then you could do it like this
https://codesandbox.io/s/z24nm345mp

If you need to do additional dispatching besides the results then see this example
https://codesandbox.io/s/j36jvpn8rv

mergeMap is the new name for flatMap in rxjs@6

My first example just returns the observable and doesn't use dispatch and done. However if you need to do other dispatching besides just the results then you will want to include dispatch and done (even though you won't be calling done, just needed for now to set the arity right).

Then you can dispatch the observable to kick things off.

Eventually in a future version of redux-logic you'll be able to do a mix of returning an observable and dispatching, but currently it is one or the other. You could also have the observable return different types of actions but my second example is probably easier to follow for that scenario.

@skbolton
Copy link

skbolton commented Oct 7, 2018

Thanks for the response @jeffbski this library has been awesome so far. Just to complete my understanding and to get me the rest of the way there when I am testing this logic it would never actually call complete right? In other words

return store.whenComplete(() => ...)

wouldn't actually get fired right? This would be okay because I am using a library to fake time going by and I can still test it

@hadim
Copy link
Author

hadim commented Oct 7, 2018

Thank you for this very detailed answer! That will be very useful.

@jeffbski
Copy link
Owner

jeffbski commented Oct 7, 2018

@skbolton yes, that is correct it would not complete unless you fire the cancelType, then it would actually complete.

You are both welcome!

@skbolton
Copy link

skbolton commented Oct 7, 2018

@jeffbski need any help on this project for hacktoberfest?

@jeffbski
Copy link
Owner

jeffbski commented Oct 9, 2018

Thanks for offering @skbolton. I'll give it some thought. Probably the biggest need is for more docs, like specifically a nice guidebook, or recipebook for using redux-logic in a variety of common use cases.

@skbolton
Copy link

Not sure if this is an issue with my rxjs understanding or this library. I have code that was working well that looked like this.

export const historySubscription = createLogic({
  type: constants.SUBSCRIBE_TO_USER_HISTORY.ACTION,
  cancelType: constants.SUBSCRIBE_TO_USER_HISTORY.CANCELLED,
  warnTimeout: 0,
  debounce: 500,
  processOptions: {
    successType: constants.FETCH_HISTORY.FULFILLED,
    failType: constants.FETCH_HISTORY.REJECTED
  },
  process ({ http, action }) {

    // create stream of events starting now and emitting every `x` milliseconds
    return timer(0, action.payload.pollTime || 10000).pipe(
      // concatMap waits for inner stream to complete and then concats the stream
      // to the outer stream (timer in this case).
      // since the inner stream is a http request that will emit complete when it is done
      // this works perfectly
      concatMap(() =>
        http
          .get(`/design-view/publish-histories/${action.payload.projectId}`)
          .pipe(
            map(http.getBody),
            map(({ projectPublishHistory }) => ({
              history: projectPublishHistory.history.reverse()
            }))
          )
      )
    )
  }
})

I had to add the feature that a pending action would get dispatched (the .ACTION) call below. This dispatch is to know when we are pending on a network request going out polling for data. The change led me to this.

export const historySubscription = createLogic({
  type: constants.SUBSCRIBE_TO_USER_HISTORY.ACTION,
  cancelType: constants.SUBSCRIBE_TO_USER_HISTORY.CANCELLED,
  warnTimeout: 0,
  debounce: 500,
  processOptions: {
    dispatchMultiple: true
  },
  process ({ http, action }, dispatch) {

    // create stream of events starting now and emitting every `x` milliseconds
    return timer(0, action.payload.pollTime || 10000).pipe(
      // this is the pending on loading history action
      tap(() => dispatch({ type: constants.FETCH_HISTORY.ACTION })),
      // concatMap waits for inner stream to complete and then concats the stream
      // to the outer stream (timer in this case).
      // since the inner stream is a http request that will emit complete when it is done
      // this works perfectly
      concatMap(() =>
        http
          .get(`/design-view/publish-histories/${action.payload.projectId}`)
          .pipe(
            map(http.getBody),
            map(({ projectPublishHistory }) => ({
              history: projectPublishHistory.history.reverse()
            }))
          )
      )
    )
    .subscribe({
      next (payload) {
        return dispatch({ type: constants.FETCH_HISTORY.FULFILLED, payload })
      },
      error (err) {
        return dispatch({ type: constants.FETCH_HISTORY.REJECTED, payload: err, error: true })
      }
    })
  }
})

This works except the cancellation stopped working. I was able to get it back by doing this.

export const historySubscription = createLogic({
  type: constants.SUBSCRIBE_TO_USER_HISTORY.ACTION,
  cancelType: constants.SUBSCRIBE_TO_USER_HISTORY.CANCELLED,
  warnTimeout: 0,
  debounce: 500,
  processOptions: {
    dispatchMultiple: true
  },
  process ({ http, action, action$ }, dispatch) {

    // create stream of events starting now and emitting every `x` milliseconds
    return timer(0, action.payload.pollTime || 10000).pipe(
      takeUntil(
        action$.pipe(filter(act => act.type === constants.SUBSCRIBE_TO_USER_HISTORY.CANCELLED))
      ),
      // this is the pending on loading history action
      tap(() => dispatch({ type: constants.FETCH_HISTORY.ACTION })),
      // concatMap waits for inner stream to complete and then concats the stream
      // to the outer stream (timer in this case).
      // since the inner stream is a http request that will emit complete when it is done
      // this works perfectly
      concatMap(() =>
        http
          .get(`/design-view/publish-histories/${action.payload.projectId}`)
          .pipe(
            map(http.getBody),
            map(({ projectPublishHistory }) => ({
              history: projectPublishHistory.history.reverse()
            }))
          )
      )
    )
    .subscribe({
      next (payload) {
        return dispatch({ type: constants.FETCH_HISTORY.FULFILLED, payload })
      },
      error (err) {
        return dispatch({ type: constants.FETCH_HISTORY.REJECTED, payload: err, error: true })
      }
    })
  }
})

Is there something I am doing wrong or is this a bug?

@jeffbski
Copy link
Owner

I think what you have is fine. By including the dispatch the automatic dispatchReturn was disabled and thus it wasn't automatically subscribing to your observable and that it wasn't getting cancelled. Cancellation should stop sending out the dispatch results, but the observable is probably still continuing. Compared with when you return the observable it subscribes for your and also cancels the observable automatically too.

There's a couple ways of possibly simplifying this.

One way would be to use the cancelled$ rather than creating your own.

export const historySubscription = createLogic({
  type: constants.SUBSCRIBE_TO_USER_HISTORY.ACTION,
  cancelType: constants.SUBSCRIBE_TO_USER_HISTORY.CANCELLED,
  warnTimeout: 0,
  debounce: 500,
  processOptions: {
    dispatchMultiple: true
  },
  process ({ http, action, action$, cancelled$ }, dispatch) {

    // create stream of events starting now and emitting every `x` milliseconds
    return timer(0, action.payload.pollTime || 10000).pipe(
      takeUntil(cancelled$),
      // this is the pending on loading history action
      tap(() => dispatch({ type: constants.FETCH_HISTORY.ACTION })),
      // concatMap waits for inner stream to complete and then concats the stream
      // to the outer stream (timer in this case).
      // since the inner stream is a http request that will emit complete when it is done
      // this works perfectly
      concatMap(() =>
        http
          .get(`/design-view/publish-histories/${action.payload.projectId}`)
          .pipe(
            map(http.getBody),
            map(({ projectPublishHistory }) => ({
              history: projectPublishHistory.history.reverse()
            }))
          )
      )
    )
    .subscribe({
      next (payload) {
        return dispatch({ type: constants.FETCH_HISTORY.FULFILLED, payload })
      },
      error (err) {
        return dispatch({ type: constants.FETCH_HISTORY.REJECTED, payload: err, error: true })
      }
    })
  }
})

Another idea that might work fine is to set dispatchReturn to true and then it should allow you to do both dispatching of the Fetch history action and handle what is emitted from the returned observable. Since it is subscribing automatically to the observable, it should cancel appropriately without additional help.

export const historySubscription = createLogic({
  type: constants.SUBSCRIBE_TO_USER_HISTORY.ACTION,
  cancelType: constants.SUBSCRIBE_TO_USER_HISTORY.CANCELLED,
  warnTimeout: 0,
  debounce: 500,
  processOptions: {
    successType: constants.FETCH_HISTORY.FULFILLED,
    failType: constants.FETCH_HISTORY.REJECTED,
    dispatchMultiple: true,
    dispatchReturn: true,
  },
  process ({ http, action }, dispatch) {

    // create stream of events starting now and emitting every `x` milliseconds
    return timer(0, action.payload.pollTime || 10000).pipe(
      // this is the pending on loading history action
      tap(() => dispatch({ type: constants.FETCH_HISTORY.ACTION })),
      // concatMap waits for inner stream to complete and then concats the stream
      // to the outer stream (timer in this case).
      // since the inner stream is a http request that will emit complete when it is done
      // this works perfectly
      concatMap(() =>
        http
          .get(`/design-view/publish-histories/${action.payload.projectId}`)
          .pipe(
            map(http.getBody),
            map(({ projectPublishHistory }) => ({
              history: projectPublishHistory.history.reverse()
            }))
          )
      )
    )
  }
})

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants