Postboy Help

Error Handling and Edge Cases

Postboy is a message bus, not an error boundary. Errors that occur inside a message handler belong to your application code. The bus does not catch, transform, or propagate exceptions. If a handler fails, it should convert that failure into a meaningful result and send it back through the callback flow.

  1. Returning a nullable result The simplest way to communicate failure is to make the response type nullable. The handler calls msg.finish(null) when the operation cannot produce a valid result.

import {AppPostboyService} from '@shared/services/app-postboy.service'; import {ConnectMessage} from '@artstesh/postboy'; import {Subject} from 'rxjs'; import {GetUserQuery} from './messages/queries/get-user.query'; export class UserService { constructor(private http: HttpClient, private postboy: AppPostboyService) { this.postboy.exec(new ConnectMessage(GetUserQuery, new Subject<GetUserQuery>())); this.postboy.sub(GetUserQuery).subscribe((msg) => { this.http.get<User>(`/api/users/${msg.userId}`). subscribe({ next: (user) => msg.finish(user), error: () => msg.finish(null), // network error or server error → null }); }); } }
  1. Discriminated union for richer error context When the sender needs to distinguish between different failure modes, model the response as a discriminated union.

// response type type LoadResult = | { status: 'success'; data: User } | { status: 'not-found' } | { status: 'error'; code: string }; export class GetUserQuery extends PostboyCallbackMessage<LoadResult> { static readonly ID = 'user.load'; constructor(public readonly userId: string) { super(); } }

The handler constructs the appropriate shape.

this.postboy.sub(GetUserQuery).subscribe((msg) => { this.http.get<User>(/api/us ers / $ { msg.userId } ). subscribe({ next: (user) => msg.finish({status: 'success', data: user}), error: (err) => { if (err.status === 404) { msg.finish({status: 'not-found'}); } else { msg.finish({status: 'error', code: err.message}); } }, }); });

The sender then exhaustively checks the result.

this.postboy.fireCallback(new GetUserQuery('123')).subscribe((result) => { switch (result.status) { case 'success': console.log(result.data.name); break; case 'not-found': console.log('User not found'); break; case 'error': console.error('Failed to load user:', result.code); break; } });
  1. Returning a fallback value For scenarios where a missing result can safely be replaced by a sensible default, the handler can finish with a fallback.

this.postboy.sub(SearchProductsQuery).subscribe((msg) => { this.http.get<Product[]>('/api/products', {params: {q: msg.query}}) .pipe(takeUntil(msg.cancel$)) .subscribe({ next: (products) => msg.finish(products), error: () => msg.finish([]), // empty list as fallback }); });
  1. What not to do Do not throw an error from the handler expecting the bus to catch it.

// ❌ Wrong: exception is lost inside the subscription this.postboy.sub(GetUserQuery).subscribe((msg) => { throw new Error('Something went wrong'); });

Postboy does not see this exception. It stays inside the handler’s subscription and triggers Angular’s default error handler at best. The callback flow remains unresolved, leaking the sender’s subscription.

Key points Always call msg.finish(...) explicitly, even to report a failure.

Use nullable types, discriminated unions, or fallback values to represent errors inside the callback contract.

The bus guarantees only that a result can be delivered; it’s up to your handler to translate application failures into that result.

The sender’s subscription will not error out — handle both success and failure by inspecting the returned value.

24 апреля 2026