Home > Net >  Future catchError function doesn't catch error when its not chained immediately
Future catchError function doesn't catch error when its not chained immediately

Time:01-17

consider this code:
EXAMPLE A:

Future<void> fakeCall() async {
  await Future.delayed(const Duration(milliseconds: 300), () {
    throw MyError('myException');
  });
}

Future<void> fetch() async {
  Future<void> testFuture = fakeCall();

  testFuture.whenComplete(() {
    print('when completed');
  });
  testFuture.catchError((e) {
    print('on error');
  });
  return testFuture;
}

void main() async {
 try{
  await fetch();
  }on MyError catch(e){
     //getting here but one still leaks!!
  }
}

when calling await fetch() I expect only catchError to be called. but the Error somehow leaks, so basically two were thrown...one caught by catchError and one is leaked outside as an unhandled error.

but when using it the proposed way (chained one after the other):
EXAMPLE B:

Future<void> fakeCall() async {
  await Future.delayed(const Duration(milliseconds: 300), () {
    throw MyError('myException');
  });
}

Future<void> fetch() async {
  Future<void> testFuture = fakeCall().whenComplete(() {
    print('when completed');
  }).catchError((e) {
    print('on error');
  });

  return testFuture;
}

void main() async {
 try{
  await fetch();
  }on MyError catch(e){
     //getting here and nothing leaks!!
  }
}

No error leaking occurs. It just left me very curious, can someone explain why writing it in the first way makes the error to leak?


update

originally I forgot to add try/catch around the fetch function, so you might think the error leaked for this reason. but notice, the error still leaks when not chaining the one rror properly (first example)

CodePudding user response:

The error is leaked because you are awaiting the result on a Future which ends up being completed with an error in:

await fetch();

It is important to understand that a Future object is just a value which gets a value sometimes in the future and this value can either be a value or an error.

The catchError() is a way to subscribe some code to be executed if the Future gets completed as an error. The method returns a new future with the logic that it gets the origin value in case of the origin future gets completed without any error. But if the origin gets completed with an error, the catchError() runs a specified method and returns the value after this execution.

The catchError() does not change anything about the origin Future and you can therefore call catchError() as many times you want.

I should add that I think it is better to use a traditional try...catch (combined with await) inside async-marked functions instead of playing with catchError() since it makes the code more readable and less confusing:

Future<void> fetch() async {
  try {
    await fakeCall();
  } catch (e) {
    print('on error');
  } finally {
    print('when completed');
  }
}

void main() async {
  await fetch();
}

Outputs:

on error
when completed

Update after question was changed

The problem with "Example B" is that you assume that whenComplete() does handle any errors. But if you read the the documentation:

The future returned by this call, f, will complete the same way as this future unless an error occurs in the action call, or in a Future returned by the action call. If the call to action does not return a future, its return value is ignored.

https://api.dart.dev/stable/2.15.1/dart-async/Future/whenComplete.html

So the returned Future have not changed its behavior so if the origin Future completes with an error, the same is happening with the Future created from whenComplete(). And since you are not attaching any error handling on this Future, your program fails.

  •  Tags:  
  • Related