Search code examples
node.jsstatefsmxstate

XState: Wait for response of invoked function


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?


Solution

  • 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