Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/statelyai/xstate/llms.txt

Use this file to discover all available pages before exploring further.

The fromPromise() function creates actor logic from an async function that returns a promise. Actors created from promise logic emit the resolved value and complete when the promise settles.

Signature

function fromPromise<
  TOutput,
  TInput = NonReducibleUnknown,
  TEmitted extends EventObject = EventObject
>(
  promiseCreator: ({
    input,
    system,
    self,
    signal,
    emit
  }: {
    input: TInput;
    system: AnyActorSystem;
    self: PromiseActorRef<TOutput>;
    signal: AbortSignal;
    emit: (emitted: TEmitted) => void;
  }) => PromiseLike<TOutput>
): PromiseActorLogic<TOutput, TInput, TEmitted>;

Parameters

promiseCreator
function
required
A function that returns a Promise. Receives an object with:
input
TInput
Data provided to the promise actor when created or invoked.
self
PromiseActorRef<TOutput>
Reference to the promise actor itself.
system
AnyActorSystem
The actor system to which the promise actor belongs.
signal
AbortSignal
An AbortSignal that is aborted when the actor is stopped.
emit
(emitted: TEmitted) => void
Function to emit custom events to subscribers.

Returns

PromiseActorLogic
ActorLogic
Actor logic that can be used with createActor() or invoked in a state machine.

Usage

Basic Example

import { fromPromise, createActor } from 'xstate';

const promiseLogic = fromPromise(async () => {
  const response = await fetch('https://api.example.com/data');
  const data = await response.json();
  return data;
});

const actor = createActor(promiseLogic);

actor.subscribe((snapshot) => {
  console.log('Status:', snapshot.status);
  console.log('Output:', snapshot.output);
});

actor.start();
// Status: active
// ... after promise resolves
// Status: done
// Output: { ... data ... }

With Input

import { fromPromise, createActor } from 'xstate';

type Input = { userId: string };
type Output = { name: string; email: string };

const fetchUser = fromPromise<Output, Input>(async ({ input }) => {
  const response = await fetch(`/api/users/${input.userId}`);
  return response.json();
});

const actor = createActor(fetchUser, {
  input: { userId: '123' }
});

actor.subscribe((snapshot) => {
  if (snapshot.status === 'done') {
    console.log('User:', snapshot.output);
    // User: { name: 'John', email: 'john@example.com' }
  }
});

actor.start();

Invoking in a Machine

import { setup, fromPromise } from 'xstate';

const fetchData = fromPromise(async ({ input }: { input: { url: string } }) => {
  const response = await fetch(input.url);
  return response.json();
});

const machine = setup({
  actors: {
    fetchData
  }
}).createMachine({
  initial: 'loading',
  states: {
    loading: {
      invoke: {
        src: 'fetchData',
        input: { url: '/api/data' },
        onDone: {
          target: 'success',
          actions: ({ event }) => {
            console.log('Data loaded:', event.output);
          }
        },
        onError: {
          target: 'failure',
          actions: ({ event }) => {
            console.error('Failed to load:', event.error);
          }
        }
      }
    },
    success: {},
    failure: {}
  }
});

Using AbortSignal

import { fromPromise, createActor } from 'xstate';

const fetchWithAbort = fromPromise(async ({ signal }) => {
  const response = await fetch('https://api.example.com/data', {
    signal
  });
  return response.json();
});

const actor = createActor(fetchWithAbort);
actor.start();

// Stop the actor (aborts the fetch)
setTimeout(() => {
  actor.stop();
}, 1000);

Emitting Progress Events

import { fromPromise, createActor } from 'xstate';

type ProgressEvent = { type: 'progress'; percent: number };

const uploadFile = fromPromise<string, { file: File }, ProgressEvent>(
  async ({ input, emit }) => {
    const formData = new FormData();
    formData.append('file', input.file);

    const xhr = new XMLHttpRequest();
    
    xhr.upload.addEventListener('progress', (e) => {
      if (e.lengthComputable) {
        const percent = (e.loaded / e.total) * 100;
        emit({ type: 'progress', percent });
      }
    });

    return new Promise((resolve, reject) => {
      xhr.addEventListener('load', () => resolve(xhr.responseText));
      xhr.addEventListener('error', () => reject(new Error('Upload failed')));
      xhr.open('POST', '/upload');
      xhr.send(formData);
    });
  }
);

const actor = createActor(uploadFile, {
  input: { file: new File(['content'], 'test.txt') }
});

actor.on('progress', ({ percent }) => {
  console.log(`Upload progress: ${percent.toFixed(1)}%`);
});

actor.subscribe((snapshot) => {
  if (snapshot.status === 'done') {
    console.log('Upload complete:', snapshot.output);
  }
});

actor.start();

Error Handling

import { fromPromise, createActor } from 'xstate';

const riskyOperation = fromPromise(async ({ input }: { input: { id: string } }) => {
  const response = await fetch(`/api/items/${input.id}`);
  
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}: ${response.statusText}`);
  }
  
  return response.json();
});

const actor = createActor(riskyOperation, {
  input: { id: '999' }
});

actor.subscribe({
  next: (snapshot) => {
    if (snapshot.status === 'error') {
      console.error('Error:', snapshot.error);
    }
  },
  error: (err) => {
    console.error('Actor error:', err);
  }
});

actor.start();

Dynamic Input from Context

import { setup, fromPromise } from 'xstate';

const fetchUser = fromPromise(async ({ input }: { input: { id: string } }) => {
  const response = await fetch(`/api/users/${input.id}`);
  return response.json();
});

const machine = setup({
  actors: { fetchUser }
}).createMachine({
  context: {
    userId: '123'
  },
  initial: 'idle',
  states: {
    idle: {
      on: {
        FETCH: 'loading'
      }
    },
    loading: {
      invoke: {
        src: 'fetchUser',
        input: ({ context }) => ({
          id: context.userId
        }),
        onDone: 'success',
        onError: 'failure'
      }
    },
    success: {},
    failure: {}
  }
});

Chaining Promises

import { fromPromise } from 'xstate';

const fetchAndProcess = fromPromise(async ({ input }: { input: { url: string } }) => {
  // First fetch
  const response = await fetch(input.url);
  const data = await response.json();
  
  // Process the data
  const processed = await processData(data);
  
  // Additional async operation
  const result = await saveToDatabase(processed);
  
  return result;
});

async function processData(data: any) {
  return new Promise((resolve) => {
    setTimeout(() => resolve({ ...data, processed: true }), 100);
  });
}

async function saveToDatabase(data: any) {
  return fetch('/api/save', {
    method: 'POST',
    body: JSON.stringify(data)
  }).then(r => r.json());
}

Snapshot

The promise actor snapshot has the following structure:
interface PromiseSnapshot<TOutput, TInput> {
  status: 'active' | 'done' | 'error' | 'stopped';
  output: TOutput | undefined;
  error: unknown;
  input: TInput | undefined;
}

Status Values

  • 'active' - Promise is pending
  • 'done' - Promise resolved successfully
  • 'error' - Promise rejected with an error
  • 'stopped' - Actor was stopped before promise settled

Behavior

  • One-time execution: The promise creator is called once when the actor starts
  • No event handling: Sending events to promise actors has no effect
  • Automatic completion: The actor completes when the promise settles
  • Abort support: The provided signal is aborted when the actor is stopped
  • Error propagation: Promise rejections become error snapshots

Type Parameters

TOutput
type
The type of the resolved promise value.
TInput
type
default:"NonReducibleUnknown"
The type of the input data.
TEmitted
EventObject
default:"EventObject"
The type of events that can be emitted.

See Also

Build docs developers (and LLMs) love