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

Handling errors in void Dart function marked async

提问人:Dan R 提问时间:9/16/2023 最后编辑:Dan R 更新时间:9/17/2023 访问量:52

问:

我正在设计一个 API,并希望处理用户错误地标记函数的情况。voidasync

下面列出了简化代码:

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');
  });
}

如果标记该函数,则程序输出符合预期,错误为 抛出、捕获、处理,然后执行块:asyncfinally

$ 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()errorThrowervoid

有什么方法可以在不更改签名的情况下捕获和处理错误?run()

dart 错误处理 async-await try-catch void

评论


答:

1赞 pixel 9/16/2023 #1

看,我可以解释一下您的预期结果和实际结果的差异。

当你将一个函数标记为 时,这意味着该函数返回一种特殊类型的对象,称为 .A 表示可能完成也可能未完成的潜在操作。当您调用一个函数时,它会立即返回,并且函数内部的工作发生在 .这就是 Dart 能够执行 .asyncFutureFutureasynchronousasyncbackgroundasynchronous programming

在您的函数中,您将另一个称为 run 的函数作为参数。当你将 run 标记为 时,这意味着它正在返回一个 ,当你调用它时,函数执行不会暂停以等待 run 函数完成。相反,它立即与循环。testasyncFuture<void>without awaitcontinues

这就是为什么您看到循环在不等待 run 函数完成的情况下运行的原因。它同时运行,并且它抛出的块不会被捕获到块中,因为块在运行函数完成之前已经完成。exceptionstry/catchtry/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';
  });
}

现在,想想它是如何给出预期的结果的!如果没有收到,请回复。

评论

1赞 Dan R 9/16/2023
谢谢,您的回复很有帮助,但没有回答问题。在我的示例中,我无法更改 的签名。“当你调用一个函数时,它会立即返回,并且函数内部的工作在后台进行”这句话并不完全准确,因为函数中的代码是同步执行的,直到遇到第一个等待的未来。run()asyncasync
0赞 Dan R 9/16/2023
我修改了回调,以突出显示函数中的代码是同步执行的,直到遇到第一个语句。run()asyncawait
1赞 jamesdlin 9/17/2023 #2

这是怎么回事?

标记的函数会自动转换,以便返回值和抛出的对象包装在 s 中。失败的 s 必须通过向 Future.catchError 注册回调、在块内使用 using (这是注册回调的语法糖)或设置 Zone 错误处理程序(我认为这超出了本答案的范围)来处理。从概念上讲,你可以认为失败是在同步函数已经离开它的 - 块之后从 Dart 事件循环中抛出异常。asyncFutureFutureawaitFuturetryFuture.catchErrorFuturetesttrycatch

有什么方法可以在不更改签名的情况下捕获和处理错误?run()

T Function() void Function() 的子类型可替代)。因此,在编译时,您无法阻止在预期 a 的地方传递 a。这也是为什么像这样的事情不能阻止异步回调(即使这几乎总是一个坏主意)并且需要 linter 的支持Future<void> Function()void Function()Iterable.forEach

您可以添加运行时检查:

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();
  }
  ...
}

请注意,如果要等待完成(成功与否),则其本身需要异步,并且需要返回 .在这一点上,你不妨总是假设它可能是异步的,并且无条件地:testruntestFuturerunawait

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