Cybermuse.io
Cybermuse.io2w ago

Error handling abort on unstable_httpBatchStreamLink

When using a streaming response with unstable_httpBatchStreamLink and cancelling a request with an AbortController, the client will throw an error. The error is an instance of a generic error rather than a TRPCClientError. I'm trying to think of a solid way to swallow the abort error.
4 Replies
Alex / KATT 🐱
can you create an issue if you think it's a bug? it should be a TRPCClientError with an AbortError-cause
Cybermuse.io
Cybermuse.ioOP2w ago
I don't know that its necessarily a bug. The error in question is defined here. https://github.com/trpc/trpc/blob/52a57eaa9c12394778abf5f0e6b52ec6f46288ed/packages/server/src/unstable-core-do-not-import/stream/jsonl.ts#L338 And you have a test for it. https://github.com/trpc/trpc/blob/52a57eaa9c12394778abf5f0e6b52ec6f46288ed/packages/tests/server/streaming.test.ts#L325 I'm just not sure of how to correctly handle it. Just doing a string comparison on the error message seems to do the trick. That feels a little fragile though.
if (error instanceof Error && error.message === 'Invalid response or stream interrupted')
if (error instanceof Error && error.message === 'Invalid response or stream interrupted')
GitHub
trpc/packages/server/src/unstable-core-do-not-import/stream/jsonl.t...
🧙‍♀️ Move Fast and Break Nothing. End-to-end typesafe APIs made easy. - trpc/trpc
GitHub
trpc/packages/tests/server/streaming.test.ts at 52a57eaa9c12394778a...
🧙‍♀️ Move Fast and Break Nothing. End-to-end typesafe APIs made easy. - trpc/trpc
Alex / KATT 🐱
show me the client code you're doing and what issue it's causing please like, show me the full code if you abort something you should also not really care about the result, so unsure why you do
Cybermuse.io
Cybermuse.ioOP2w ago
I have a call to get a streaming text response called generateMessage.
const generateMessage = async () => {
pendingMessage.value = true

try {
const iterable = await streamingClient.messages.generate.mutate(chatId, {signal: abortController.signal})
for await (const text of iterable) {
// process text
scrollMessages('smooth')
}
} catch (error) {
// Ignore abort errors
if (error instanceof Error && error.message === 'Invalid response or stream interrupted') return
throw error
} finally {
console.log('done pending')
pendingMessage.value = false
}
}
const generateMessage = async () => {
pendingMessage.value = true

try {
const iterable = await streamingClient.messages.generate.mutate(chatId, {signal: abortController.signal})
for await (const text of iterable) {
// process text
scrollMessages('smooth')
}
} catch (error) {
// Ignore abort errors
if (error instanceof Error && error.message === 'Invalid response or stream interrupted') return
throw error
} finally {
console.log('done pending')
pendingMessage.value = false
}
}
If the user wants to stop the response early, then calling abort seems like the correct way to do so. Calling abort causes the TRPC client to throw an error. That error should be swallowed, not propogated up or displayed. However any other errors, like a network failure or validation error should be propagated. Its a very similar story with a plain browser fetch request. When you abort, it throws an error. In that case, the error will be an instanceof DOMException with a name of "AbortError". Handling the error by message seems to be workable. The generateMessage function works. Just that handling by error message seems a bit fragile. The error name is just "error", and the message seems to imply that this error could likely be used for other types of errors that should be propogated.