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
A function that returns a Promise. Receives an object with:Data provided to the promise actor when created or invoked.
Reference to the promise actor itself.
The actor system to which the promise actor belongs.
An AbortSignal that is aborted when the actor is stopped.
emit
(emitted: TEmitted) => void
Function to emit custom events to subscribers.
Returns
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 ... }
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
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