• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2019 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'dart:async';
6
7/// Runs [fn] with special handling of asynchronous errors.
8///
9/// If the execution of [fn] does not throw a synchronous exception, and if the
10/// [Future] returned by [fn] is completed with a value, then the [Future]
11/// returned by [asyncGuard] is completed with that value if it has not already
12/// been completed with an error.
13///
14/// If the execution of [fn] throws a synchronous exception, then the [Future]
15/// returned by [asyncGuard] is completed with an error whose object and
16/// stack trace are given by the synchronous exception.
17///
18/// If the execution of [fn] results in an asynchronous exception that would
19/// otherwise be unhandled, then the [Future] returned by [asyncGuard] is
20/// completed with an error whose object and stack trace are given by the
21/// asynchronous exception.
22///
23/// After the returned [Future] is completed, whether it be with a value or an
24/// error, all further errors resulting from the execution of [fn] both
25/// synchronous and asynchronous are ignored.
26///
27/// Rationale:
28///
29/// Consider the following snippet:
30/// ```
31/// try {
32///   await foo();
33///   ...
34/// } catch (e) {
35///   ...
36/// }
37/// ```
38/// If the [Future] returned by `foo` is completed with an error, that error is
39/// handled by the catch block. However, if `foo` spawns an asynchronous
40/// operation whose errors are unhandled, those errors will not be caught by
41/// the catch block, and will instead propagate to the containing [Zone]. This
42/// behavior is non-intuitive to programmers expecting the `catch` to catch all
43/// the errors resulting from the code under the `try`.
44///
45/// As such, it would be convenient if the `try {} catch {}` here could handle
46/// not only errors completing the awaited [Future]s it contains, but also
47/// any otherwise unhandled asynchronous errors occuring as a result of awaited
48/// expressions. This is how `await` is often assumed to work, which leads to
49/// unexpected unhandled exceptions.
50///
51/// [asyncGuard] is intended to wrap awaited expressions occuring in a `try`
52/// block. The behavior described above gives the behavior that users
53/// intuitively expect from `await`. Consider the snippet:
54/// ```
55/// try {
56///   await asyncGuard(() async {
57///     var c = Completer();
58///     c.completeError('Error');
59///   });
60/// } catch (e) {
61///   // e is 'Error';
62/// }
63/// ```
64/// Without the [asyncGuard] the error 'Error' would be propagated to the
65/// error handler of the containing [Zone]. With the [asyncGuard], the error
66/// 'Error' is instead caught by the `catch`.
67Future<T> asyncGuard<T>(Future<T> Function() fn) {
68  final Completer<T> completer = Completer<T>();
69
70  runZoned<void>(() async {
71    try {
72      final T result = await fn();
73      if (!completer.isCompleted) {
74        completer.complete(result);
75      }
76    } catch (e, s) {
77      if (!completer.isCompleted) {
78        completer.completeError(e, s);
79      }
80    }
81  }, onError: (dynamic e, StackTrace s) {
82    if (!completer.isCompleted) {
83      completer.completeError(e, s);
84    }
85  });
86
87  return completer.future;
88}
89