Errors & Retrying
By default, errors thrown inside steps are retried. Additionally, Workflow DevKit provides two new types of errors you can use to customize retries.
Default Retrying
By default, steps retry up to 3 times on arbitrary errors. You can customize the number of retries by adding a maxRetries property to the step function
async function callApi(endpoint: string) {
"use step";
const response = await fetch(endpoint);
if (response.status >= 500) {
// Any uncaught error gets retried
throw new Error("Uncaught exceptions get retried!");
}
return response.json();
}
callApi.maxRetries = 5; // Set a custom number of retriesSteps get enqueued immediately after the failure. Read on to see how this can be customized.
Intentional Errors
When your step needs to intentionally throw an error and skip retrying, simply throw a FatalError.
import { FatalError } from 'workflow';
async function callApi(endpoint: string) {
"use step";
const response = await fetch(endpoint);
if (response.status >= 500) {
// Any uncaught error gets retried
throw new Error("Uncaught exceptions get retried!");
}
if (response.status === 404) {
throw new FatalError("Resource not found. Skipping retries.");
}
return response.json();
}Customize Retry Behavior
When you need to customize the delay on the retry, use RetryableErrorand set the retryAfter property.
import { FatalError, RetryableError } from 'workflow';
async function callApi(endpoint: string) {
"use step";
const response = await fetch(endpoint);
if (response.status >= 500) {
throw new Error("Uncaught exceptions get retried!");
}
if (response.status === 404) {
throw new FatalError("Resource not found. Skipping retries.");
}
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After');
// Delay the retry until after a timeout
throw new RetryableError("Too many requests. Retrying...", {
retryAfter: parseInt(retryAfter)
});
}
return response.json();
}Advanced Example
This final example combines everything we've learnt, along with getStepMetadata.
import { FatalError, RetryableError, getStepMetadata } from 'workflow';
async function callApi(endpoint: string) {
"use step";
const metadata = getStepMetadata();
const response = await fetch(endpoint);
if (response.status >= 500) {
// Exponential backoffs
throw new RetryableError("Backing off...", { retryAfter: metadata.attempt ** 2 });
}
if (response.status === 404) {
throw new FatalError("Resource not found. Skipping retries.");
}
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After');
// Delay the retry until after a timeout
throw new RetryableError("Too many requests. Retrying...", {
retryAfter: parseInt(retryAfter)
});
}
return response.json();
}
callApi.maxRetries = 5;