Skip to content

Sleeping and retrying

This guide details how to sleep a Workflow and/or configure retries for a Workflow step.

Sleep a Workflow

You can set a Workflow to sleep as an explicit step, which can be useful when you want a Workflow to wait, schedule work ahead, or pause until an input or other external state is ready.

Sleep for a relative period

Use step.sleep to have a Workflow sleep for a relative period of time:

await step.sleep("sleep for a bit", "1 hour")

The second argument to step.sleep accepts both number (seconds) or a human-readable format, such as "1 minute" or "26 hours". The accepted units for step.sleep when used this way are as follows:

| "second"
| "minute"
| "hour"
| "day"
| "week"
| "month"
| "year"

Sleep until a fixed date

Use step.sleepUntil to have a Workflow sleep to a specific Date: this can be useful when you have a timestamp from another system or want to "schedule" work to occur at a specific time (e.g. Sunday, 9AM UTC).

// sleepUntil accepts a Date object as its second argument
const workflowsLaunchDate = Date.parse("24 Oct 2024 13:00:00 UTC");
await step.sleepUntil("sleep until X times out", workflowsLaunchDate)

You can also provide a Unix timestamp (seconds since the Unix epoch) directly to sleepUntil.

Retry steps

Each call to step.do in a Workflow accepts an optional StepConfig, which allows you define the retry behaviour for that step.

If you do not provide your own retry configuration, Workflows applies the following defaults:

const defaultConfig: WorkflowStepConfig = {
retries: {
limit: 5,
delay: 10000,
backoff: 'exponential',
},
timeout: '10 minutes',
};

When providing your own StepConfig, you can configure:

  • The total number of attempts to make for a step
  • The delay between attempts (accepts both number (ms) or a human-readable format)
  • What backoff algorithm to apply between each attempt: any of constant, linear, or exponential
  • When to timeout (in duration) before considering the step as failed (including during a retry attempt)

For example, to limit a step to 10 retries and have it apply an exponential delay (starting at 10 seconds) between each attempt, you would pass the following configuration as an optional object to step.do:

let someState = step.do("call an API", {
retries: {
limit: 10, // The total number of attempts
delay: "10 seconds", // Delay between each retry
backoff: "exponential" // Any of "constant" | "linear" | "exponential";
},
timeout: "30 minutes",
}, async () => { /* Step code goes here /* }

Force a Workflow instance to fail

You can also force a Workflow instance to fail and not retry by throwing a NonRetryableError from within the step.

This can be useful when you detect a terminal (permanent) error from an upstream system (such as an authentication failure) or other errors where retrying would not help.

// Import the NonRetryableError definition
import { WorkflowEntrypoint, WorkflowStep, WorkflowEvent } from 'cloudflare:workers';
import { NonRetryableError } from 'cloudflare:workflows';
// In your step code:
export class MyWorkflow extends WorkflowEntrypoint<Env, Params> {
async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
await step.do("some step", async () => {
if !(event.data) {
throw new NonRetryableError("event.data did not contain the expected payload")
}
})
}
}

The Workflow instance itself will fail immediately, no further steps will be invoked, and the Workflow will not be retried.

Catch Workflow errors

Any uncaught exceptions that propagate to the top level, or any steps that reach their retry limit, will cause the Workflow to end execution in an Errored state.

If you want to avoid this, you can catch exceptions emitted by a step. This can be useful if you need to trigger clean-up tasks or have conditional logic that triggers additional steps.

To allow the Workflow to continue its execution, surround the intended steps that are allowed to fail with a try-catch block.

...
await step.do('task', async () => {
// work to be done
});
try {
await step.do('non-retryable-task', async () => {
// work not to be retried
throw new NonRetryableError('oh no');
});
} catch(e as Error) {
console.log(`Step failed: ${e.message}`);
await step.do('clean-up-task', async () => {
// Clean up code here
});
}
// the Workflow will not fail and will continue its execution
await step.do('next-task', async() => {
// more work to be done
});
...