处理void Dart函数中标记为async的错误

k5ifujac  于 2023-09-28  发布在  其他
关注(0)|答案(2)|浏览(87)

我正在设计一个API,希望处理用户错误地标记void函数async的情况。
简化代码如下所示:

void test(void Function() run) {
  try {
    for (var i = 0; i < 3; i++) {
      print(i);
      run();
    }
  } catch (e) {
    print(e);
  } finally {
    print('in finally block.');
  }
}

void errorThrower(String message) {
  print('in errorThrower ');
  throw message;
}

void main(List<String> args) {
  test(() async{  // <-------- marked async by API user's mistake
    print('in run ...');
    errorThrower('error thrown in test');
  });
}

如果函数被not标记为async,则程序输出与预期的一样,错误被抛出,捕获,处理,并执行finally块:

$ dart main.dart
0
in run ...
in errorThrower 
error thrown in test
in finally block.

但是,如果函数标记为async,则控制台输出为:

$ dart main.dart 
0
in run ...
in errorThrower 
1
in run ...
in errorThrower 
2
in run ...
in errorThrower 
in finally block.
Unhandled exception:
error thrown in test
#0      errorThrower (file:///home/dan/WORK/DartProjects/benchmark_runner/bin/throws_test.dart:16:3)
#1      main.<anonymous closure> (file:///main.dart:22:5)
#2      test (file:///main.dart:5:10)
#3      main (file:///main.dart:20:3)
#4      _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:294:33)
#5      _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:189:12)

这是怎么回事?我尝试了一个调试器,程序进入run(),然后errorThrower,然后循环继续。它看起来像一个未等待的未来错误,但我不知道如何捕获和处理它,因为void函数不能等待。
有没有什么方法可以在不改变run()签名的情况下捕获和处理错误?

cngwdvgl

cngwdvgl1#

这是怎么回事?
标记为async的函数会自动转换,以便将返回值和抛出的对象 Package 在Future s中。失败的Future s必须通过向Future.catchError注册回调来处理,通过在try块内的Future上使用using await(这是注册Future.catchError回调的语法糖),或者通过设置Zone错误处理程序(我认为这超出了这个答案的范围)。从概念上讲,您可以将失败的Future视为在 synchronoustest函数已经离开其try-catch块之后从Dart事件循环中抛出异常。
有没有什么方法可以在不改变run()签名的情况下捕获和处理错误?
A T Function() is a subtype of (is substitutable for) void Function()。因此,在编译时,您无法阻止Future<void> Function()传递到需要void Function()的位置。这也是为什么像Iterable.forEach这样的东西不能阻止异步回调(even though it's almost always a bad idea)和would need support from the linter
您可以添加运行时检查:

void test(void Function() run) {
  if (run is Future<void> Function()) {
    throw ArgumentError("test: Cannot be used with asynchronous callbacks."); 
  }
  ...
}

或者,如果你只是想吞下错误:

void test(void Function() run) {
  ...
  if (run is Future<void> Function()) {
    run().catchError((e) => print(e));
  } else {
    run();
  }
  ...
}

请注意,如果您希望test等待run完成(无论成功与否),那么test本身将需要异步,并且需要返回Future。在这一点上,你也可以总是假设run可能是异步的,并且无条件地await它:

Future<void> test(FutureOr<void> Function() run) async {
  try {
    ...
    await run();
  ...
}
monwx1rj

monwx1rj2#

看,我可以解释你的预期结果和实际结果的差异。
当你将一个函数标记为async时,这意味着该函数返回一个特殊类型的对象,称为FutureFuture表示可能完成或未完成的潜在asynchronous操作。当你调用一个async函数时,它会立即返回,函数内部的工作发生在background中。这就是使Dart能够执行asynchronous programming的原因。
test函数中,您将另一个名为run的函数作为参数。当你将run标记为async时,这意味着它返回一个Future<void>,当你调用它without await时,函数的执行不会暂停以等待run函数完成。相反,它立即使用循环continues
这就是为什么您看到循环在运行,而无需等待run函数完成。它是并发运行的,它抛出的exceptions不会被try/catch块捕获,因为在run函数完成之前,try/catch块已经完成。
下面的代码将导致您预期的答案:

import 'dart:async';

Future<void> test(Future<void> Function() run) async {
  try {
    for (var i = 0; i < 3; i++) {
      print(i);
      await run(); // Await the execution of the async function.
    }
  } catch (e) {
    print(e);
  } finally {
    print('In finally block.');
  }
}

Future<void> main(List<String> args) async {
  await test(() async {
    throw 'In void function erroneously marked async';
  });
}

现在,想想它是如何给予预期的结果!如果你没有得到答复。

相关问题