Skip to content

docs: onCancel #2024

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 3, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 103 additions & 2 deletions docs/upgrade-to-v4.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,107 @@ tasks.onComplete(({ ctx, result }) => {
});
```

### onCancel

<Note>Available in v4.0.0-beta.12 and later.</Note>

You can now define an `onCancel` hook that is called when a run is cancelled. This is useful if you want to clean up any resources that were allocated for the run.

```ts
tasks.onCancel(({ ctx, signal }) => {
console.log("Run cancelled", signal);
});
```

You can use the `onCancel` hook along with the `signal` passed into the run function to interrupt a call to an external service, for example using the [streamText](https://ai-sdk.dev/docs/reference/ai-sdk-core/stream-text) function from the AI SDK:

```ts
import { logger, tasks, schemaTask } from "@trigger.dev/sdk";
import { streamText } from "ai";
import { z } from "zod";

export const interruptibleChat = schemaTask({
id: "interruptible-chat",
description: "Chat with the AI",
schema: z.object({
prompt: z.string().describe("The prompt to chat with the AI"),
}),
run: async ({ prompt }, { signal }) => {
const chunks: TextStreamPart<{}>[] = [];

// 👇 This is a global onCancel hook, but it's inside of the run function
tasks.onCancel(async () => {
// We have access to the chunks here, and can save them to the database
await saveChunksToDatabase(chunks);
});

try {
const result = streamText({
model: getModel(),
prompt,
experimental_telemetry: {
isEnabled: true,
},
tools: {},
abortSignal: signal, // 👈 Pass the signal to the streamText function, which aborts with the run is cancelled
onChunk: ({ chunk }) => {
chunks.push(chunk);
},
});

const textParts = [];

for await (const part of result.textStream) {
textParts.push(part);
}

return textParts.join("");
} catch (error) {
if (error instanceof Error && error.name === "AbortError") {
// streamText will throw an AbortError if the signal is aborted, so we can handle it here
} else {
throw error;
}
}
},
});
```

The `onCancel` hook can optionally wait for the `run` function to finish, and access the output of the run:

```ts
import { logger, task } from "@trigger.dev/sdk";
import { setTimeout } from "node:timers/promises";

export const cancelExampleTask = task({
id: "cancel-example",
// Signal will be aborted when the task is cancelled 👇
run: async (payload: { message: string }, { signal }) => {
try {
// We pass the signal to setTimeout to abort the timeout if the task is cancelled
await setTimeout(10_000, undefined, { signal });
} catch (error) {
// Ignore the abort error
}

// Do some more work here

return {
message: "Hello, world!",
};
},
onCancel: async ({ runPromise }) => {
// You can await the runPromise to get the output of the task
const output = await runPromise;
},
});
```

<Note>
You will have up to 30 seconds to complete the `runPromise` in the `onCancel` hook. After that
point the process will be killed.
</Note>

### Improved middleware and locals

Our task middleware system is now much more useful. Previously it only ran "around" the `run` function, but now we've hoisted it to the top level and it now runs before/after all the other hooks.
Expand Down Expand Up @@ -704,7 +805,7 @@ export const myTask = task({
id: "my-task",
onStart: ({ payload, ctx }) => {},
// The run function still uses separate parameters
run: async ( payload, { ctx }) => {},
run: async (payload, { ctx }) => {},
});
```

Expand Down Expand Up @@ -760,4 +861,4 @@ const batchHandle = await tasks.batchTrigger([
// Now you need to call runs.list()
const runs = await batchHandle.runs.list();
console.log(runs);
```
```