mobx.dart icon indicating copy to clipboard operation
mobx.dart copied to clipboard

Effect callback from asyncAction will run on rootZone, which causes UncaughtException.

Open Elvis5566 opened this issue 3 years ago • 3 comments

In flutter, we use a custom zone to catch all exceptions from async operation. The effect callback should run on the same zone as calling reaction, however, if we change observable via asyncAction, the effect callback will run on RootZone. This seems a serious issue, because we cannot get exception report from production build. Am I misunderstanding? or is there a way to make effect run on the guarded zone even if we use AsyncAction?

Update: This issue causes by using two AsyncActions, say we have two asyncActions A and B, A triggers reaction, then in the reaction handler, we call B. If there is an exception in B, neither guardedZone nor reaction onError can catch the exception.

sample_uncaughtException

Elvis5566 avatar Sep 30 '22 02:09 Elvis5566

@Elvis5566 https://github.com/mobxjs/mobx.dart/issues/853#issuecomment-1265203660

By default, MobX will catch and re-throw exceptions happening in your code to make sure that a reaction in one exception does not prevent the scheduled execution of other, possibly unrelated, reactions. This means exceptions are not propagated back to the original causing code and therefore you won't be able to catch them using try/catch.

By disabling error boundaries, exceptions can escape derivations. This might ease debugging, but might leave MobX and by extension your application in an unrecoverable broken state.

amondnet avatar Oct 03 '22 10:10 amondnet

I realized that this issue has nothing to do with disableErrorBoundaries. Say we have two asyncActions A and B, A triggers reaction, then in the reaction handler, we call B. If there is an exception in B, neither guardedZone nor reaction onError can catch that exception. Here is sample code.

sample_uncaughtException

Elvis5566 avatar Oct 11 '22 03:10 Elvis5566

@Elvis5566 Yes there is a problem when using asyncAction with reaction. I'm making an asyncReaction but it takes some time. There is currently no clear workaround.

  1. Do not use asyncAction in reaction. Use sync action or do not throw exception.

    reaction((_) => store.value, (_) {
          store.async2().catchError((error) {
            print('catchError :$error');
          });
        });
    
  2. Use result.

reaction((_) => store.value, (_) {
      store.async2().then((result) {
        if (result.isError ) {
          print(result.asError!.error);
        }
      });
    });
import 'dart:async';

import 'package:async/async.dart';
import 'package:mobx/mobx.dart';

// Include generated file
part 'counter.g.dart';

// This is the class used by rest of your codebase
class Counter = _Counter with _$Counter;

// The store-class
abstract class _Counter with Store {
  @observable
  int value = 0;

  @action
  Future<void> incrementAsync() async {
    await Future.delayed(Duration(milliseconds: 200));
    value++;
  }

  @action
  Future<Result<void>> async2() async {
    try {
      await Future.delayed(Duration(milliseconds: 200));
      throw Exception("test2");
    } catch (e) {
      // don't throw exceptions.
      print('error');
      return Result.error(e);
    }
  }
}

amondnet avatar Oct 12 '22 05:10 amondnet