Describe the bug
I've found an issue when canceling a server-streaming gRPC-web request using an AbortController.
The call correctly aborts, but instead of throwing a ConnectError with Code.Canceled (value 1), it throws an error with the message "BodyStreamBuffer was aborted" and a code of 20.
As per the gRPC specification, a client-side cancellation should be mapped to the Canceled status. Furthermore, the code 20 does not exist in the official Code enum, which makes it impossible to reliably check for this specific error type in a standard way (e.g., error.code === Code.Canceled). This can break application logic that needs to differentiate between a cancellation and other types of network or server errors.
The following example uses the standard ElizaService and cancels the server-streaming introduce call after 500ms.
import { createClient, ConnectError, Code } from '@connectrpc/connect';
import { createGrpcWebTransport } from '@connectrpc/connect-web';
import { ElizaService } from '@buf/connectrpc_eliza.connectrpc_es/eliza_connect.js';
import type { IntroduceRequest } from '@buf/connectrpc_eliza.connectrpc_es/eliza_pb.js';
// Client setup
const transport = createGrpcWebTransport({
baseUrl: 'https://demo.connectrpc.com',
});
const client = createClient(ElizaService, transport);
// Function to call the service and cancel it
async function callAndCancel() {
const controller = new AbortController();
// Schedule cancellation
setTimeout(() => {
console.log('Aborting request...');
controller.abort();
}, 500);
try {
const request: IntroduceRequest = { name: 'User' };
const stream = client.introduce(request, { signal: controller.signal });
for await (const response of stream) {
console.log('Received response:', response.sentence);
}
console.log('Stream finished without error.');
} catch (e) {
if (e instanceof ConnectError) {
console.error('Caught a ConnectError:');
console.error(' > Message:', e.message);
console.error(' > Code:', e.code); // This is 20
console.error(' > Is Code.Canceled?', e.code === Code.Canceled); // This is false
} else {
console.error('Caught an unexpected error:', e);
}
}
}
callAndCancel();
Expected Behavior
The catch block should receive a ConnectError where error.code is equal to Code.Canceled (with a value of 1). The console output should look like this:
Caught a ConnectError:
> Message: BodyStreamBuffer was aborted
> Code: 1
> Is Code.Canceled? true
Actual Behavior
The catch block receives a ConnectError where error.code is 20 and the message is "BodyStreamBuffer was aborted". The console output is:
Caught a ConnectError:
> Message: BodyStreamBuffer was aborted
> Code: 20
> Is Code.Canceled? false
Environment (please complete the following information):
- @connectrpc/connect:
2.1.1
- @connectrpc/connect-web:
2.1.1
- @bufbuild/protobuf:
2.11.0
- Browser:
Google Chrome 144.0.7559.133
Describe the bug
I've found an issue when canceling a server-streaming gRPC-web request using an
AbortController.The call correctly aborts, but instead of throwing a
ConnectErrorwithCode.Canceled(value 1), it throws an error with the message"BodyStreamBuffer was aborted"and a code of20.As per the gRPC specification, a client-side cancellation should be mapped to the
Canceledstatus. Furthermore, the code20does not exist in the officialCodeenum, which makes it impossible to reliably check for this specific error type in a standard way (e.g.,error.code === Code.Canceled). This can break application logic that needs to differentiate between a cancellation and other types of network or server errors.The following example uses the standard
ElizaServiceand cancels the server-streamingintroducecall after 500ms.Expected Behavior
The
catchblock should receive aConnectErrorwhereerror.codeis equal toCode.Canceled(with a value of 1). The console output should look like this:Actual Behavior
The
catchblock receives aConnectErrorwhereerror.codeis20and the message is"BodyStreamBuffer was aborted". The console output is:Environment (please complete the following information):
2.1.12.1.12.11.0Google Chrome 144.0.7559.133