Inngest Errors
Inngest automatically handles errors and retries for you. You can use standard errors or use included Inngest errors to control how Inngest handles errors.
Standard errors
All Error
objects are handled by Inngest and retried automatically. This includes all standard errors like TypeError
and custom errors that extend the Error
class. You can throw errors in the function handler or within a step.
export default inngest.createFunction(
{ id: "import-item-data" },
{ event: "store/import.requested" },
async ({ event }) => {
// throwing a standard error
if (!event.itemId) {
throw new Error("Item ID is required");
}
// throwing an error within a step
const item = await step.run('fetch-item', async () => {
const response = await fetch(`https://api.ecommerce.com/items/${event.itemId}`);
if (response.status === 500) {
throw new Error("Failed to fetch item from ecommerce API");
}
// ...
});
}
);
Prevent any additional retries
Use NonRetriableError
to prevent Inngest from retrying the function or step. This is useful when the type of error is not expected to be resolved by a retry, for example, when the error is caused by an invalid input or when the error is expected to occur again if retried.
import { NonRetriableError } from "inngest";
export default inngest.createFunction(
{ id: "mark-store-imported" },
{ event: "store/import.completed" },
async ({ event }) => {
try {
const result = await database.updateStore(
{ id: event.data.storeId },
{ imported: true }
);
return result.ok === true;
} catch (err) {
// Passing the original error via `cause` enables you to view the error in function logs
throw new NonRetriableError("Store not found", { cause: err });
}
}
);
Parameters
new NonRetriableError(message: string, options?: { cause?: Error }): NonRetriableError
- Name
message
- Type
- string
- Required
- required
- Description
The error message.
- Name
options
- Type
- object
- Required
- optional
- Description
Retry after a specific period of time
Use RetryAfterError
to control when Inngest should retry the function or step. This is useful when you want to delay the next retry attempt for a specific period of time, for example, to more gracefully handle a race condition or backing off after hitting an API rate limit.
If RetryAfterError
is not used, Inngest will use the default retry backoff policy.
inngest.createFunction(
{ id: "send-welcome-sms" },
{ event: "app/user.created" },
async ({ event, step }) => {
const { success, retryAfter } = await twilio.messages.create({
to: event.data.user.phoneNumber,
body: "Welcome to our service!",
});
if (!success && retryAfter) {
throw new RetryAfterError("Hit Twilio rate limit", retryAfter);
}
}
);
Parameters
new RetryAfterError(
message: string,
retryAfter: number | string | date,
options?: { cause?: Error }
): RetryAfterError
- Name
message
- Type
- string
- Required
- required
- Description
The error message.
- Name
retryAfter
- Type
- number | string | date
- Required
- required
- Description
The specified time to delay the next retry attempt. The following formats are accepted:
number
- The number of milliseconds to delay the next retry attempt.string
- A time string, parsed by the ms package, such as"30m"
,"3 hours"
, or"2.5d"
.date
- ADate
object.
- Name
options
- Type
- object
- Required
- optional
- Description
Step errors v3.12.0+
After a step exhausts all of its retries, it will throw a StepError
which can be caught and handled in the function handler if desired.
inngest.createFunction(
{ id: "send-weather-forecast" },
{ event: "weather/forecast.requested" },
async ({ event, step }) => {
let data;
try {
data = await step.run('get-public-weather-data', async () => {
return await fetch('https://api.weather.com/data');
});
} catch (err) {
// err will be an instance of StepError
// Handle the error by recovering with a different step
data = await step.run('use-backup-weather-api', async () => {
return await fetch('https://api.stormwaters.com/data');
});
}
// ...
}
);
Support for handling step errors is available in the Inngest TypeScript SDK starting from version 3.12.0. Prior to this version, wrapping a step in try/catch will not work correctly.
Attempt counter
The current attempt number is passed in as input to the function handler. attempt
is a zero-index number that increments for each retry. The first attempt will be 0
, the second 1
, and so on. The number is reset after a successfully executed step.
inngest.createFunction(
{ id: "generate-summary" },
{ event: "blog/post.created" },
async ({ attempt }) => {
// `attempt` is the zero-index attempt number
await step.run('call-llm', async () => {
if (attempt < 2) {
// Call OpenAI's API two times
} else {
// After two attempts to OpenAI, try a different LLM, for example, Mistral
}
});
}
);
Stack traces
When calling functions that return Promises, await the Promise to ensure that the stack trace is preserved. This applies to functions executing in different cycles of the event loop, for example, when calling a database or an external API. This is especially useful when debugging errors in production.
inngest.createFunction(
{ id: "update-recent-usage" },
{ event: "app/update-recent-usage" },
async ({ event, step }) => {
// ...
await step.run("update in db", () => doSomeWork(event.data));
// ...
}
);
Please note that immediately returning the Promise will not include a pointer to the calling function in the stack trace. Awaiting the Promise will ensure that the stack trace includes the calling function.