ai
Version:
AI SDK by Vercel - build apps like ChatGPT, Claude, Gemini, and more with a single interface for any model using the Vercel AI Gateway or go direct to OpenAI, Anthropic, Google, or any other model provider.
84 lines (69 loc) • 2.59 kB
text/mdx
---
title: onEnd not called when stream is aborted
description: Troubleshooting onEnd callback not executing when streams are aborted with toUIMessageStream
---
When using `toUIMessageStream` with an `onEnd` callback, the callback may not execute when the stream is aborted. This happens because the abort handler immediately terminates the response, preventing the `onEnd` callback from being triggered.
```tsx
// Server-side code where onEnd isn't called on abort
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: __MODEL__,
messages: await convertToModelMessages(messages),
abortSignal: req.signal,
});
return createUIMessageStreamResponse({
stream: toUIMessageStream({
stream: result.stream,
onEnd: async ({ isAborted }) => {
// This isn't called when the stream is aborted!
if (isAborted) {
console.log('Stream was aborted');
// Handle abort-specific cleanup
} else {
console.log('Stream completed normally');
// Handle normal completion
}
},
}),
});
}
```
When a stream is aborted, the response is immediately terminated. Without proper handling, the `onEnd` callback has no chance to execute, preventing important cleanup operations like saving partial results or logging abort events.
Add `consumeSseStream: consumeStream` to the `createUIMessageStreamResponse` configuration. This ensures that abort events are properly captured and forwarded to the `onEnd` callback, allowing it to execute even when the stream is aborted.
```tsx
// other imports...
import {
consumeStream,
createUIMessageStreamResponse,
toUIMessageStream,
} from 'ai';
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: __MODEL__,
messages: await convertToModelMessages(messages),
abortSignal: req.signal,
});
return createUIMessageStreamResponse({
stream: toUIMessageStream({
stream: result.stream,
onEnd: async ({ isAborted }) => {
// Now this WILL be called even when aborted!
if (isAborted) {
console.log('Stream was aborted');
// Handle abort-specific cleanup
} else {
console.log('Stream completed normally');
// Handle normal completion
}
},
}),
consumeSseStream: consumeStream, // This enables onEnd to be called on abort
});
}
```