Xstate: Wait for response of invoked function

Created on 5 Jun 2019  路  3Comments  路  Source: davidkpiano/xstate

I am planning to use XState for managing states in the backend of my application. When an api is called, a function will be called on successful state change. The result of the function call has to be returned as response of the api.

    // Returns a Promise, e.g.:
    // {
    //   id: 42,
    //   name: 'David',
    //   friends: [2, 3, 5, 7, 9] // friend IDs
    // }
    function getUserInfo(context) {
    return fetch('/api/users/#{context.userId}').then(response =>
     response.json()
    );
    }

    // Returns a Promise
    function getUserFriends(context) {
    const { friends } = context.user;

    return Promise.all(
     friends.map(friendId =>
      fetch('/api/users/#{context.userId}/').then(response => response.json())
      )
     );
    }

    const friendsMachine = Machine({
     id: 'friends',
     context: { userId: 42, user: undefined, friends: undefined },
     initial: 'gettingUser',
     states: {
      gettingUser: {
       invoke: {
        src: getUserInfo,
        onDone: {
          target: 'gettingFriends',
          actions: assign({
            user: (context, event) => event.data
          })
        }
       }
      },
      gettingFriends: {
       invoke: {
        src: getUserFriends,
        onDone: {
          target: 'success',
          actions: assign({
            friends: (context, event) => event.data
          })
        }
       }
      },
     success: {
      type: 'final'
     }
    }
    });


    interpret(friendsMachine).start()

I want the output of this of getUserFriends sent as a response from my api. How to wait for the transition and all the invocations to be completed?

documentation question

All 3 comments

You can use onDone (read the docs on invoking promises 馃摉)

Here's an example Express app that waits sequentially for 2 promises to finish, and then sends that data:

function eventuallyGet(value) {
  return new Promise(res => {
    setTimeout(() => {
      res(value);
    }, 1000)
  })
}

const getUserMachine = Machine({
  initial: 'fetchingName',
  context: {
    user: undefined
  },
  states: {
    fetchingName: {
      invoke: {
        src: () => eventuallyGet('David'),
        onDone: {
          target: 'fetchingDetails',
          actions: assign({
            user: (ctx, e) => ({
              ...ctx.user,
              name: e.data
            })
          })
        }
      }
    },
    fetchingDetails: {
      invoke: {
        src: () => eventuallyGet({ location: 'Florida' }),
        onDone: {
          target: 'success',
          actions: assign({
            user: (ctx, e) => ({
              ...ctx.user,
              ...e.data
            })
          })
        }
      }
    },
    success: {
      type: 'final',
      data: {
        user: ctx => ctx.user
      }
    }
  }
});

app.get('/user', function(request, response) {
  interpret(getUserMachine)
    .onDone(e => {
      response.json(e.data);
    })
    .start();
});

You can see the code here: https://glitch.com/~pleasant-relish

@davidkpiano that's a good candidate for Usage with Express within Recipes.

I can send a PR if you want.

Thanks for helping me out.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

drmikecrowe picture drmikecrowe  路  3Comments

dakom picture dakom  路  3Comments

laurentpierson picture laurentpierson  路  3Comments

pke picture pke  路  3Comments

hnordt picture hnordt  路  3Comments