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

composable / higher order effects #34

Closed
yoshuawuyts opened this issue Jun 1, 2016 · 11 comments
Closed

composable / higher order effects #34

yoshuawuyts opened this issue Jun 1, 2016 · 11 comments
Projects
Milestone

Comments

@yoshuawuyts
Copy link
Member

Sometimes multiple actions need to be chained to make a thing happen. For example: logging a user out means that:

  • local storage credentials must be wiped
  • all individual state values must be wiped
  • tokens must be invalidated on the server
  • when all that's done, navigate to a new view

redux has a concept of redux-saga to handle this. It's basically a way of combining multiple actions into a higher-order flow. Pretty damn interesting, and definitely useful. Actually I built exactly this stuff last year in the form of barracks.

So umm, I think probably the right way of dealing with this is to allow for callbacks from within effects so they can be composed into higher order effects (which we can then refer to as sagas, although they'd simply be a pattern).

An example:

const http = require('choo/http')

app.model({
  effects: {
    binbaz: (state, action, send) => {
      http.get('/foo/bar', function (err, res, body) {
        if (err) return send('oh no!', err)
        send()
      })
    },
    foobar: (state, action, send) => {
      send('binbaz', function () {
        console.log('hello world!')
        send()
      })
    }
  }
})

I feel like send() loses a bit of its semantic meaning here, perhaps an extra, optional cb() argument should be added? Anyway, I hope the idea comes across. I feel like the idea is there, but I'm overlooking quirks and the consumer-facing API isn't quite where it could be - suggestions are heaps welcome! - Cheers ✨

See Also

@ahdinosaur
Copy link
Collaborator

another example is how to implement sagas in tom, which is the same pattern for inu.

from my understanding, for effects to work as sagas, you must be able to subscribe to all future actions and dispatch any number of actions at any time.

@yoshuawuyts yoshuawuyts mentioned this issue Jun 7, 2016
13 tasks
@yoshuawuyts yoshuawuyts modified the milestone: 3.0.0 Jun 7, 2016
@yoshuawuyts
Copy link
Member Author

dammit, tom and inu are nailing it - gonna re-read their docs a couple of times and figure out what they're doing. Feel they're miles ahead hah

@yoshuawuyts
Copy link
Member Author

I've been giving this a lot of thought and I think that what's needed is a callback in effects to signify it's done executing and handle control back to the function that initiated it:

const http = require('choo/http')

app.model({
  effects: {
    makeXhrRequest: (state, action, send, cb) => {
      http.get('/foo/bar', function (err, res, body) {
        if (err) return cb(err)
        cb()
      })
    },
    foobar: (state, action, send, cb) => {
      send('makeXhrRequest', function (err, res) {
        if (err) {
          console.log('all is bad D:')
          cb()
        } else {
          send('makeXhrRequest', function (err, res) {
            if (err) {
              console.log('all is bad D:')
              cb()
            } else {
              console.log('all is good :D')
              cb()
            }
          })
        }
      })
    }
  }
})

Obv in a real world scenario we'd use proper async abstractions such as map-limit or pull-stream, but I hope the point comes across. Does this make sense?

@yoshuawuyts
Copy link
Member Author

Should prob make the callback optional by doing a Function.prototype.length check for the params + not pass the same send() function into the views as the models to prevent accidental leaking of callback values into the views (which would break unidirectionality)

@kristoferjoseph
Copy link

kristoferjoseph commented Jun 20, 2016

A very similar pattern has arisen from my redux action code.
Only difference is %s/send/dispatch/g %s/cb/next/g

Careful with calling them sagas though since people have a very specific coupling of syntax i.e. generators etc. with that name.

@yoshuawuyts
Copy link
Member Author

ah yeah you're right, at this point I reckon calling it "composable effects" or "higher order effects" is probabably better - nice one!

If we're going to build choo / yo-yo guide we should probably put a migration guide for react in there hah

@kristoferjoseph
Copy link

👍 on having a migration guide.
You could just have these be covered in the async section of the user guide.
tbh the fact that you needed to add a middleware to support async with redux was a code smell.
I also really like that this approach does not ram promises down my throat. I would be very happy to see that be an add-on if ever introduced.

@yoshuawuyts yoshuawuyts changed the title support sagas? composable / higher order effects Jun 20, 2016
@bitwiselove
Copy link

bitwiselove commented Jun 23, 2016

@yoshuawuyts Could you write up an example of using pull-stream to manage that callback hell?

@yoshuawuyts
Copy link
Member Author

@bitwiselove I'd love to, but I'm slightly swamped in getting v3 out first (choo's suddenly gotten popular haha) - perhaps take a look at inu, https://pull-stream.github.io/ or ask @ahdinosaur nicely (':

I'm sure we'll get more of this out there post v3 - just trying to get through this initial spike of popularity and breaking changes unscathed rn haha

@ahdinosaur
Copy link
Collaborator

hey @bitwiselove, do you have a specific real-world use case in mind? feel free to make an issue on inu or pull-stream with any questions, happy to help. 🐱

@yoshuawuyts
Copy link
Member Author

Closing as 3.0 is imminent; wanna make sure all issues are taken care of. Releasing soooon™ ✨

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

No branches or pull requests

4 participants