Skip to content
Version: XState v4

Promises

The most common type of actors you’ll invoke are promise actors. Promise actors allow you to await the result of a promise before deciding what to do next.

import { createMachine, assign } from 'xstate';

// Function that returns a promise
// This promise might resolve with, e.g.,
// { name: 'David', location: 'Florida' }
const fetchUser = (userId: number) =>
fetch(`url/to/user/${userId}`).then((response) => response.json());

const userMachine = createMachine(
{
context: {
userId: 42,
},
invoke: { src: 'fetchUser' },
},
{
// `actors` in v5
services: {
fetchUser: (context) => {
return fetchUser(context.userId);
},
},
},
);

When a promise actor is resolved, a done.invoke event is sent back to the machine which includes its data, placed under the data property. For example:

{
type: 'done.invoke.<id>',
data: {
name: 'David',
location: 'Florida'
}
}

You can listen for this event by providing an onDone to the invoke attribute:

import { createMachine, assign } from 'xstate';

// Function that returns a promise
// This promise might resolve with, e.g.,
// { name: 'David', location: 'Florida' }
const fetchUser = (userId: number) =>
fetch(`url/to/user/${userId}`).then((response) => response.json());

const userMachine = createMachine(
{
context: {
userId: 42,
},
invoke: {
src: 'fetchUser',
onDone: [
{
cond: 'hasData',
actions: 'logUserData',
},
],
},
},
{
// `actors` in v5
services: {
fetchUser: (context) => {
return fetchUser(context.userId);
},
},
guards: {
hasData: (context, event) => {
return Boolean(event.data);
},
},
actions: {
logUserData: (context, event) => {
console.log(event.data);
},
},
},
);

If the state where the invoked promise is active is exited before the promise settles, the result of the promise is discarded.

If the promise errors, an error.platform.<id> event will be fired, with the error on the data attribute. You can listen for this error by adding onError to your invoke attribute:

import { createMachine, assign } from 'xstate';

// Function that returns a promise
// This promise might resolve with, e.g.,
// { name: 'David', location: 'Florida' }
const fetchUser = (userId: number) =>
fetch(`url/to/user/${userId}`).then((response) => response.json());

const userMachine = createMachine(
{
context: {
userId: 42,
},
invoke: {
src: 'fetchUser',
onError: {
actions: 'consoleLogError',
},
},
},
{
actions: {
consoleLogError: (context, event) => {
console.log(event.data.message);
},
},
},
);

In the example above, if fetchUser fails, the error message will be logged to the console.

If the onError transition is missing and the Promise is rejected, the error will be ignored, which is why it’s important always to define onError transitions.

The onDone and onError transitions are just like any other transition. You can use conds, fire actions and target other states. These transitions give you a declarative way of handling successes and errors.

onDone in invoke vs. onDone in states

Note that the onDone in invoke and the onDone on states are different. The onDone for states is triggered when its child reaches a final state.