Skip to content

Exception Management

d-markey edited this page Jan 29, 2026 · 2 revisions

When working with background workers in Squadron, exceptions that occur in a worker thread must be serialized to be sent back to the main thread (or whoever called the worker). By default, Squadron handles standard Dart exceptions by wrapping them in a WorkerException. However, you may want to use custom exception types to convey more specific error information.

The WorkerException class

All exceptions that occur during task execution and need to cross thread boundaries should inherit from WorkerException. This class ensures that the exception can be properly serialized and includes support for stack traces and command identifiers.

Implementing a Custom Exception

To create a custom exception, you need to:

  1. Inherit from WorkerException.
  2. Implement the serialize() method. This method must return a List where the first element is a unique type identifier (a String).
  3. Provide a static deserialization method or a top-level function that can reconstruct the exception from the list.

Example

Here is an example of a custom exception for a database service:

import 'package:squadron/squadron.dart';

class DatabaseException extends WorkerException {
  DatabaseException(this.errorCode, String message, [StackTrace? stackTrace, int? command])
      : super(message, stackTrace, command);

  final int errorCode;

  // Unique identifier for this exception type
  static const String typeId = 'DB_ERROR';

  @override
  List serialize() => [
        typeId,
        message,
        stackTrace?.toString(),
        command,
        errorCode,
      ];

  static DatabaseException? deserialize(List props) {
    if (props[0] == typeId) {
      return DatabaseException(
        props[4] as int,
        props[1] as String,
        SquadronException.loadStackTrace(props[2] as String?),
        (props[3] as num?)?.toInt(),
      );
    }
    return null;
  }
}

Registering Custom Exceptions

For Squadron to know how to reconstruct your custom exception on the receiving side, you must register its deserializer with the ExceptionManager.

Every IWorker (including Worker and WorkerPool) has an exceptionManager. You should register your custom exceptions before starting the worker or pool.

Registering with a Worker

final myWorker = MyWorker();
myWorker.exceptionManager.register(DatabaseException.typeId, DatabaseException.deserialize);

Registering with a Worker Pool

When using a WorkerPool, you should register the exception with the pool's exceptionManager. The pool will automatically pass this manager to all workers it creates.

final myPool = MyWorkerPool();
myPool.exceptionManager.register(DatabaseException.typeId, DatabaseException.deserialize);

Note: The registration must happen on the "client" side (typically the main thread) so it can deserialize the exceptions it receives from its workers. If your workers also communicate with each other, they may also need to have these exceptions registered.

Why Inherit from WorkerException?

Inheriting from WorkerException provides several benefits:

  • Platform Neutrality: It works seamlessly across both Native (Isolates) and Web (Web Workers) platforms.
  • Context Preservation: It carries the original error message and stack trace across the thread boundary, along with the command ID that caused the error.
  • Automatic Wrapping: If a worker method throws an error that is NOT a SquadronException, Squadron will automatically wrap it in a WorkerException using error.toString() as the message.

Clone this wiki locally