Skip to content

Error Handling

Failures are data, not exceptions. A throwing handler, rejected promise, or unhandled internal event never escapes send(). The actor enters the built-in terminal __error state and records the failure in snapshot().error.

  • send() never throws. Worst case: machine dies to __error.
  • Subscribers that throw are swallowed; machine and other subscribers carry on.
  • Constructor is the only exception. Bad setup throws immediately (programmer error, not runtime).

Synthesized by the actor, not in your states list. Terminal.

  • Thrown value recorded alongside last state, context, triggering event, and ErrorReason
  • All queued events dropped, pending effects aborted
  • Every subsequent send is a no-op
  • snapshot().done is true; change and done lifecycle events fire

undefined while alive. When set, carries ErrorInfo:

FieldMeaning
errorthrown value or rejection
statelast known good state
contextcontext at that state
eventtriggering event (__init at construction)
reasonErrorReason tag

ErrorReason values: transition, effect, unhandled, budget, output, internal, async.

__error kills the machine. Better: catch, emit an internal error event, let a transition handle it. The error state can leave via a back handler or similar.

m.effect(submitting, {
name: "chargeCard",
fn: ({ signal, emit }) => {
chargeCard(cardNumber)
.then((id) => {
if (!signal.aborted) emit(paymentOk.create({ orderId: id }));
})
.catch((r) => {
if (!signal.aborted) emit(paymentFail.create({ reason: String(r) }));
});
},
});

Subscribe to death. Fired with the full ErrorInfo. Seeded: if the machine died before you subscribed, the last ErrorInfo replays immediately.

checkout.on("error", (info) => {
console.error(`${info.reason} on ${info.event.type}:`, info.error);
});

Bring a dead machine back. Caller supplies state and context. Breaks determinism (no longer “same inputs, same trace”). Effects not re-run, timers not re-armed. No-op on a live machine.

checkout.recover({ state: payment, context: { paymentInfo: { cardNumber: "..." } } });