State machine advent: Asynchronous code in XState (19/24)
Tags: typescript, javascript, react
Published: 2019-12-19 22:59:36

Most applications are filled with asynchronous code. It's a shame when state management libraries do not support to write such code out of the box. Luckily, in XState multiple ways to handle asynchronicity exist. Today, we want to explore how to deal with promises and their superset - async functions. 

Since most things in XState are modeled using actions and state transitions, let's take a look at how those two concepts translate to the invocation of a promise.

A [promise is a state machine](https://www.promisejs.org/implementing/) that at any point in time is either ` pending | fulfilled | rejected`. When we want to call a promise within a state machine, the first thing we want to do is to represent the three possible states as state nodes inside the machine. 

Let's say we want to create a state machine that fetches cat images from an external API.

- One state node should represent the `pending` state of the promise. This is where we want to call the promise to fetch cat images. The promise will be invoked every time we enter the state node. Let's call this state node `fetching`.
- One state node should represent the `fulfilled` state of the promise. We'll call this one `success`.
- (Optionally) One state node that represents the `rejected` state of the promise. We'll call it `failure`. 

```typescript
interface CatFetchStateSchema {
  idle: {};
  fetching: {};
  success: {};
  failure: {};
}

type CatFetchEvent = { type: 'FETCH_CATS'};

interface CatFetchContext {
  /**
   * We also want to track error messages. After all, should the promise be rejected, the least we can do is to let the user know why they can't look at cat pictures 😿 (Did you know that a crying cat emoji exists? lol) 
   */
  errorMessage?: string;
  cats: any[];
}
```  

We can then implement our state machine. 

```typescript
import { Machine, assign } from 'xstate';

const catFetchMachine = Machine<CatFetchContext, CatFetchStateSchema, CatFetchEvent>({
  id: 'catFetch',
  initial: 'idle',
  context: {
    errorMessage: undefined,
    cats: [],
  },
  states: {
    idle: {
      on: {
        'FETCH_CATS': {
          target: 'fetching',
        },
      },
    },
    fetching: {
      invoke: {
        id: 'retrieveCats',
        src: (context, event) => fetchCats(),
        onDone: {
          target: 'success',
          actions: assign({ cats: (context, event) => event.data })
        },
        onError: {
          target: 'failure',
          actions: assign({ errorMessage: (context, event) => event.data })
        }
      }
    },
    success: {},
    failure: {},
  }
})
```

The `invoke` property indicates that we are invoking something that does not return a response immediately. Since the response occurs at some point in the future, we define an error and success handler. They will be called when the promise is rejected or fulfilled respectively. In the `onError` and `onDone` event handlers, we can define the next state (value of `target`) and actions. Actions are used to perform side effects such as [assigning a new value to the context](https://dev.to/codingdive/state-machine-advent-update-xstate-context-with-actions-1e9d).
Since we typically express state changes with state transitions and actions anyway, dealing with asynchronous code in XState is a breeze!

Another thing that makes me happy when dealing with async code in XState is exception management. Normally our `fetchCats` code would look something like this:

```javascript
const fetchCats = async () => {
  try {
    const catResponse = await fetch('some-cat-picture-api');
    const cats = await catResponse.json().data;
    return cats;
  } catch (error){
    console.error("Something went wrong when fetching cats 😿", error);
    // handle error
  }
}
```

Because of the `onError` handler, we have moved the exception management into our state machine. As a result, we need to ensure the promise can be rejected and can happily remove the try-catch block from the async function:  

```javascript
const fetchCats = async () => {
  const catResponse = await fetch('some-cat-picture-api');
  const cats = await catResponse.json().data;
  return cats;
}
```

___

Granted, with the [machine implementation from above](https://xstate.js.org/viz/?gist=d2a4a6727ead4024c11077fcb84bd4d2), cats will only be fetched once. We can fix this by adding some state transitions back to the `fetching` state. 

```javascript
  success: {
    on: {
      'MORE_CATS': {
        target: 'fetching'
      },
    },
  },
  failure: {
    on: {
      'RETRY': {
        target: 'fetching'
      },
    },
  },
```

Now the user can recover our machine from a `failure` state and also fetch more/different cats. 

In summary, to perform asynchronous code in XState: 

- translate the three promise states into state nodes ( `pending = fetching`, `fulfilled = success`, `rejected = failure`)
- define state transitions and actions in the error or success event handlers
- give the object that invokes the promise (technically called a [service](https://xstate.js.org/docs/guides/communication.html#the-invoke-property)) a unique id 
- ensure that promises can be rejected by removing the try-catch blocks from asynchronous functions

___

Very excited to have finally introduced the `invoke` property as we'll come back to it in the next couple of days when exploring some of the other things that can be invoked in XState.

### About this series
Throughout the first 24 days of December, I'll publish a small blog post each day teaching you about the ins and outs of state machines and statecharts.

The first couple of days will be spent on the fundamentals before we'll progress to more advanced concepts.