FormidableLabs / freactal

Clean and robust state management for React and React-like libs.
MIT License
1.65k stars 46 forks source link

effects with async/await functions #88

Closed michelmattos closed 6 years ago

michelmattos commented 6 years ago

For some reason, when I return a function at the end of an async function, it doesn't work (the function even don't get called). But when I return the result of another effect (the commented line), it just works.

const wrapComponentWithState = provideState({
  initialState: () => ({
    posts: null,
    postsPending: false
  }),
  effects: {
    setPostsPending: update((state, postsPending) => ({ postsPending })),
    setPosts: update((state, posts) => ({ posts })),
    getPosts: effects => async () => {
      await effects.setPostsPending(true)
      const result = await fetch('https://jsonplaceholder.typicode.com/posts')
      const posts = await result.json()
      await effects.setPostsPending(false)
      console.log(posts)
      //return effects.setPosts(posts)
      return state => ({ ...state, posts })
    }
  }
})
igorbarbashin commented 6 years ago

I also have problems figuring out how to make async/await working properly in effects.

It only works if I make the first function async:

getPosts: async (effects) => {
      const result = await fetch('https://jsonplaceholder.typicode.com/posts')
      const posts = await result.json()

      return state => ({ ...state, posts })
    }

But I haven't figured out how do I access state from inside of the effect. Placing async anywhere else screws up the state

bingomanatee commented 6 years ago

As far as I can tell that is a bit of a a "dead zone" in effects. They can change and be aware of state in the final (state) => newState method.

There is a "hackish" way of doing this in two stages. it takes advantage of the fact that the final lambda (state) => newState) has acces to the effects from the outer closure AND the state from its own parameter:

effectA [ (effects, param) => ... ]
effectB    [
        (effects ?) => 
         [
          (state) => 
            effects.effectA(state.foo)
           return state (unchanged)
       ]
 ]

i.e., effectB gets a state value and calls effectA with it but does NOT return the promise resulting from the effectA call. It then returns state in whatever state its in.

This is a very jury-rigged way of having an effect pull a parameter from state but it will work. Personally I'm fine with having states take parameters that I manually extract from state, as wherever I call an effect from will also have access to state.

michelmattos commented 6 years ago

@igorbarbashin It's probably because the foremost function can't return a promise but another function (the latter one can be async)

michelmattos commented 6 years ago

Ah, I misunderstood you, @igorbarbashin. You're right. The first function needs to be async. It is because the effect signature can be Effects => State => State or Effects => Promise<State => State>. Since an async function is the same as () => Promise<any> then it fufill the latter case. With that, my example can be rewritten like this:

const wrapComponentWithState = provideState({
  initialState: () => ({
    posts: null,
    postsPending: false
  }),
  effects: {
    setPostsPending: update((state, postsPending) => ({ postsPending })),
    setPosts: update((state, posts) => ({ posts })),
    getPosts: async effects => {
      await effects.setPostsPending(true)
      const result = await fetch('https://jsonplaceholder.typicode.com/posts')
      const posts = await result.json()
      await effects.setPostsPending(false)
      return state => ({ ...state, posts })
    }
  }
})