# Rollbacks
Source: https://www.inngest.com/docs/features/inngest-functions/error-retries/rollbacks

Unlike an error being thrown in the main function's body, a failing step (one that has exhausted all retries) will throw a `StepError`. This allows you to handle failures for each step individually, where you can recover from the error gracefully.

If a step failure isn't handled, the error will bubble up to the function itself, which will then be marked as failed.

Below is an attempt to use DALL-E to generate an image from a prompt, and to fall back to Midjourney if it fails. Remember that these calls are split over separate requests, making the code much more durable against timeouts, transient errors, and these dependencies on external APIs.

<GuideSelector options={[{ key: "typescript", title: "TypeScript" }, { key: "go", title: "Go" }]}>

<GuideSection show="typescript">

```ts
inngest.createFunction(
  { id: "generate-result" },
  { event: "prompt.created" },
  async ({ event, step }) => {
    // try one AI model, if it fails, try another
    let imageURL: string | null = null;
    let via: "dall-e" | "midjourney";

    try {
      imageURL = await step.run("generate-image-dall-e", () => {
        // open api call to generate image...
      });
      via = "dall-e";
    } catch (err) {
      imageURL = await step.run("generate-image-midjourney", () => {
        // midjourney call to generate image...
      });
      via = "midjourney";
    }

    await step.run("notify-user", () => {
      return pusher.trigger(event.data.channelID, "image-result", {
        imageURL,
        via,
      });
    });
  },
);
```

</GuideSection>

<GuideSection show="go">

```go
inngestgo.CreateFunction(
	client,
	inngestgo.FunctionOpts{ID: "generate-result"},
	inngestgo.EventTrigger("prompt.created", nil),
	func(ctx context.Context, input inngestgo.Input[PromptCreatedEvent]) (any, error) {
		var (
			imageURL string
			err      error
		)

		via := "dall-e"
		imageURL, err = step.Run(ctx, "generate-image-dall-e", func(ctx context.Context) (string, error) {
			// Open API call to generate image with Dall-E...
		})
		if err != nil {
			// Update how we ran the code.  This could also have been a return from the step.
			via = "midjourney"
			imageURL, err = step.Run(ctx, "generate-image-midjourney", func(ctx context.Context) (string, error) {
				// MidJourney call to generate image...
			})
		}
		if err != nil {
			return nil, err
		}

		_, err = step.Run(ctx, "notify-user", func(ctx context.Context) (any, error) {
			return pusher.Trigger(input.Event.Data.ChannelID, "image-result", map[string]string{
				"imageURL": imageURL.(string),
				"via":      via,
			})
		})
		if err != nil {
			return nil, err
		}

		return nil, nil
	},
)
```

</GuideSection>

### Simple rollbacks

With this pattern, it's possible to assign a small rollback for each step, making sure that every action is safe regardless of how many steps are being run.

<GuideSection show="typescript">

```ts
inngest.createFunction(
  { id: "add-data" },
  { event: "app/row.data.added" },
  async ({ event, step }) => {
    // ignore the error - this step is fine if it fails
    await step
      .run("non-critical-step", () => {
        return updateMetric();
      })
      .catch();

    // Add a rollback to a step
    await step
      .run("create-row", async () => {
        await createRow(event.data.rowId);
        await addDetail(event.data.entry);
      })
      .catch((err) =>
        step.run("rollback-row-creation", async () => {
          await removeRow(event.data.rowId);
        }),
      );
  },
);
```

</GuideSection>

<GuideSection show="go">

```go
inngestgo.CreateFunction(
	client,
	inngestgo.FunctionOpts{ID: "add-data"},
	inngestgo.EventTrigger("app/row.data.added", nil),
	func(ctx context.Context, input inngestgo.Input[RowDataAddedEvent]) (any, error) {
		_, _ = step.Run(ctx, "non-critical-step", func(ctx context.Context) (any, error) {
			return updateMetric()
		})

		_, err := step.Run(ctx, "create-row", func(ctx context.Context) (any, error) {
			_, err := createRow(input.Event.Data.RowID)
			if err != nil {
				return nil, err
			}
			return addDetail(input.Event.Data.Entry)
		})
		if err != nil {
			_, err = step.Run(ctx, "rollback-row-creation", func(ctx context.Context) (any, error) {
				return removeRow(input.Event.Data.RowID)
			})
			if err != nil {
				return nil, err
			}
		}

		return nil, nil
	},
)
```

</GuideSection>

</GuideSelector>