Skip to Content
🎉 gRPCity 3.0 is released. Read more →

Error

gRPCity surfaces every failure to the client side as a typed error so you can catch it precisely. Whether the failure originated in your handler, in the network, in a deadline, or in an AbortSignal, the shape of the error tells you exactly what happened.

The GrpcClientError shape

When a unary call rejects, or a stream surfaces an error from for await, the client receives a GrpcClientError with these fields:

FieldDescription
nameAlways 'GrpcClientError'. Use this to distinguish framework errors from regular ones.
codeNumeric gRPC status code. 1 is CANCELLED, 4 is DEADLINE_EXCEEDED, etc.
detailsThe status string from the server, when present.
metadataAny trailing metadata returned with the failure.
messageComposed message including the method path and server stack (handy for logging).
stackThe client-side stack at the point the error was thrown.

Example:

try { await client.sayGreet({ name: 'gRPCity' }) } catch (err) { if (err.name === 'GrpcClientError') { if (err.code === 1) { // cancelled — usually by AbortSignal or call.cancel() } else if (err.code === 4) { // deadline exceeded — bump `timeout` or shed load } else { // any other non-OK status } } else { throw err } }

Distinguishing AbortError from GrpcClientError

When you pass an AbortSignal that fires before the RPC reaches the wire, gRPCity rejects synchronously with the signal’s reason (a standard AbortError, code: 20). The call is never sent. When the signal fires during the call, gRPCity translates the abort into call.cancel() and you get a GrpcClientError with code: 1.

See the AbortSignal guide for the full picture.

Streams

The same GrpcClientError shape applies to streaming RPCs. Errors surface from the consumer side as a rejected for await:

const call = await client.serverStreamHello({ message: 'x' }) try { for await (const data of call.readAll()) { // ... } } catch (err) { // err.name === 'GrpcClientError', err.code carries the gRPC status }

Client-stream and bidi failures surface from await call.writeEnd() and from for await ... call.readAll() in the same way.

Triggering an error from the server

Just throw. gRPCity converts the thrown Error into a gRPC status on the wire; the framework guarantees that throwing inside any of the four RPC handler kinds (unary, client stream, server stream, bidi) reaches the client as a GrpcClientError.

async sayHello(call) { const metadata = call.metadata.clone() metadata.add('x-timestamp-server', 'received=' + new Date().toISOString()) call.sendMetadata(metadata) if (metadata.get('x-throw-error').length > 0) { throw new Error('throw error because x-throw-error') } return { message: `hello, ${call.request.name || 'world'}` } }

For server-stream and bidi handlers, throwing is the correct way to fail the call — gRPCity routes it through the underlying server stream’s 'error' channel so the client receives a status trailer immediately rather than waiting for its deadline.

Last updated on