Error management e non solo, un modo smart di gestire i contesti asincroni

Error management e non solo, un modo smart di gestire i contesti asincroni


In questo articolo parleremo di come gestire gli errori in modo da poterli intercettare e agire di conseguenza attraverso delle callback specifiche.

Un esempio potrebbe essere quello di una chimata API in cui siamo chimati a gestire 3 stati:

  1. Loading
  2. Success
  3. Failure

Inoltre molto spesso in Flutter ad ognuno di questi stati corrisponde uno stato della UI che va a descrivere in maniera specifica all’utente cosa sta accadendo; mostreremo un loader mentre aspettiamo la risposta del server, i valori ottenuti in caso di risposta positiva e un messaggio di errore nel caso in cui non riusciamo ad ottenere le informazioni.

Vedremo qui di seguito un modo semplice per gestire tutto questo in poche righe, sia in presenza di un singonlo valore futuro (Future) sia per una stream di valori (Stream)

Gestione dell’errore in caso di Future

Innanzitutto possiamo crearci una funzione che ci permette di richiamare il nostro metodo in maniera “safe” e si occupa di gestire eventuali eccezioni, diciamo un qualcosa del genere:

typedef FailureFromExceptionFunction = Failure Function(dynamic e);
typedef FutureOperationFunction<T> = Future<T> Function();

Future<T> runSafetyFuture<T>(
  FutureOperationFunction<T> operation, {
  FailureFromExceptionFunction? onException,
}) async {
  try {
    return await operation();
  } catch (e) {
    return Future.error(onException?.call(e) ?? e);
  }
}

A questo punto possiamo usare questa funzione per invocare un qualsiasi medodo o funzione asincrona in maniera sicura senza preoccuparci delle eccezioni.

Ok, si, va bene, ma ora? come faccio a definire cosa fare nei vari stati? Direi che potremmo farlo attraverso una extension su Future come la seguente:

typedef SuccessCallback<T> =  void Function(T);
typedef FailureCallback = void Function(dynamic);

extension FutureExtension<T> on Future<T> {
  Future<void> when({
    VoidCallback? progress,
    SuccessCallback<T>? success,
    FailureCallback? failure,
  }) async {
    progress?.call();
    await then((value) => success?.call(value)).catchError(onErrorHandler(failure));
  }
}

Come possiamo vedere nel codice precedente abbiamo un nuovo metodo when che accetta 3 callback, una per ogni stato:

  1. VoidCallback? progress che viene invocata immediatamente e può essere utilizzata per gestire un eventuale stato di caricamento;
  2. SuccessCallback<T> success che viene invocato nel caso in cui otteniamo correttamente i dati e il Future viene risolto correttamente;
  3. FailureCallback? failure che viene invocato nel caso in cui runSafetyFuture rileva un eccezione.

Gestione dell’errore in caso di Stream

In maniera similare a come avviene per i Future possiamo creare la nostra funzione che ha il compito di intercettare le eventuali eccezioni e permetterci di gestirle:

typedef StreamOperationFunction<T> = Stream<T> Function();
typedef FailureFromExceptionFunction = Failure Function(dynamic e);

Stream<T> runSafetyStream<T>(
  StreamOperationFunction<T> operation, {
  FailureFromExceptionFunction? onException,
}) {
  return operation().handleError((e) {
    throw onException?.call(e) ?? e;
  });
}

E anche in questo caso ci creiamo una extension per la gestione delle callback:

typedef SuccessCallback<T> =  void Function(T);
typedef FailureCallback = void Function(dynamic);

extension StreamExtension<T> on Stream<T> {
  void when({
    VoidCallback? progress,
    SuccessCallback<T>? success,
    FailureCallback? failure,
  }) {
    progress?.call();
    handleError(onErrorHandler(failure)).forEach((value) => success?.call(value));
  }
}

Anche in questo caso abbiamo il metodo when che accetta 3 callback, una per ogni stato:

  1. VoidCallback? progress che viene invocata immediatamente e può essere utilizzata per gestire un eventuale stato di caricamento;
  2. SuccessCallback<T> success che viene invocato nel caso in cui otteniamo correttamente i dati e il Future viene risolto correttamente;
  3. FailureCallback? failure che viene invocato nel caso in cui runSafetyFuture rileva un eccezione.