Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[2.19] Add runZonedGuarded to Zones page #4403

Merged
merged 14 commits into from
Dec 20, 2022
Merged
128 changes: 73 additions & 55 deletions src/_articles/archive/zones.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,28 +21,28 @@ obsolete: true

### Asynchronous dynamic extents

_Written by Florian Loitsch and Kathy Walrath<br>
March 2014_

This article discusses zone-related APIs in the dart:async library,
with a focus on the top-level `runZoned()` function.
with a focus on the top-level [`runZoned()`][]
and [`runZonedGuarded()`][] functions.
Before reading this article,
you should be familiar with the techniques covered in
[Futures and Error Handling](/guides/libraries/futures-error-handling).

Currently, the most common use of zones is
to handle errors raised in asynchronously executed code.
[`runZoned()`]: ({{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-async/runZoned.html)
[`runZonedGuarded()`]: ({{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-async/runZonedGuarded.html)

Developers commonly use zones to handle uncaught errors.
For example,
a simple HTTP server
might use the following code:

{% prettify dart tag=pre+code %}
[!runZoned(() {!]
[!runZonedGuarded(() {!]
HttpServer.bind('0.0.0.0', port).then((server) {
server.listen(staticFiles.serveRequest);
});
[!},
onError: (e, stackTrace) => print('Oh noes! $e $stackTrace'));!]
(error, stackTrace) => print('Oh noes! $error $stackTrace'));!]
{% endprettify %}

Running the HTTP server in a zone
Expand All @@ -51,11 +51,13 @@ errors in the server's asynchronous code.

{{site.alert.info}}
**API note:**
This use case won't always require zones.
We expect that isolates will have an API enabling you to
listen for uncaught errors.
This use case doesn't *require* zones.
The isolate API [`Isolate.run()`][] also handles
listening for uncaught errors.
{{site.alert.end}}

[`Isolate.run()`]: {{site.dart-api}}/dev/dart-isolate/Isolate/run.html

Zones make the following tasks possible:

* Protecting your app from exiting due to
Expand Down Expand Up @@ -152,33 +154,51 @@ Code might execute in other nested child zones as well,
but at a minimum it always runs in the root zone.


## Handling asynchronous errors
## Handling uncaught errors

One of the most used features of zones is
their ability to handle uncaught errors
in asynchronously executing code.
The concept is similar to a try-catch in synchronous code.

An _uncaught error_ is often caused by some code using `throw`
to raise an exception that is not handled by a `catch` statement.
Another way to produce an uncaught error is
to call `new Future.error()` or
a Completer's `completeError()` method.

Use the `onError` argument to `runZoned()` to
install a _zoned error handler_—an asynchronous error handler
that's invoked for every uncaught error in the zone.
For example:
their ability to catch and handle uncaught errors,
both synchronous and asynchronous.
An _uncaught error_ is often caused by code using `throw`
to raise an exception without an accompanying `catch`
statement to handle it.

In `async` functions, errors usually end up as the result of a Future.
Futures allow you to handle errors where you `await` the future.
Sometimes an error escapes, or a future is not `await`-ed,
in which case an error becomes an uncaught error.
Uncaught errors can crash your program.

Zones can install an _uncaught error handler_ upon creation.
When an uncaught error occurs in that zone,
rather than just crashing the program,
it calls its uncaught error handler.
(In fact, any zone that doesn't specify a handler
inherits the uncaught error handler of the root zone,
which just crashes the program on an uncaught error).

The simplest way to introduce a new zone with an uncaught error handler
is to use `runZoneGuarded`. Its `onError` callback becomes the
uncaught error handler of a new zone, and handles any
synchronous errors thrown by the call as well.

<!-- run_zoned1.dart -->
{% prettify dart tag=pre+code %}
runZoned(() {
runZonedGuarded(() {
Timer.run(() { throw 'Would normally kill the program'; });
}, onError: (error, stackTrace) {
}, (error, stackTrace) {
print('Uncaught error: $error');
});
{% endprettify %}

_See also [`Zone.fork`][], [`Zone.runGuarded`][]
and [`ZoneSpecification.uncaughtErrorHandler`][]
for more uncaught error handling methods in zones._

[`Zone.fork`]: ({{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}dart-async/Zone/fork.html)
[`Zone.runGuarded`]: ({{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}dart-async/Zone/runGuarded.html)
[`ZoneSpecification.uncaughtErrorHandler`]: ({{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}dart-async/ZoneSpecification/handleUncaughtError.html)

The preceding code has an asynchronous callback
(through `Timer.run()`) that throws an exception.
Normally this exception would be an unhandled error and reach the top level
Expand All @@ -193,16 +213,13 @@ they still execute.
As a consequence a zoned error handler might
be invoked multiple times.

Also note that an _error zone_—a zone that has an error handler—might
handle errors that originate in a child (or other descendant)
of that zone.
A zoned error handler exists in an _error zone_.
An error zone might handle errors that originate
in a descendant of that zone.
A simple rule determines where
errors are handled in a sequence of future transformations
(using `then()` or `catchError()`):

{{site.alert.note}}
Errors on Future chains never cross the boundaries of error zones.
{{site.alert.end}}
Errors on Future chains never cross the boundaries of error zones.

If an error reaches an error zone boundary,
it is treated as unhandled error at that point.
Expand All @@ -217,32 +234,32 @@ can't cross into an error zone.
<!-- run_zoned2.dart -->
{% prettify dart tag=pre+code %}
var f = new Future.error(499);
f = f.whenComplete(() { print('Outside runZoned'); });
f = f.whenComplete(() { print('Outside of zones'); });
runZoned(() {
f = f.whenComplete(() { print('Inside non-error zone'); });
});
runZoned(() {
runZonedGuarded(() {
f = f.whenComplete(() { print('Inside error zone (not called)'); });
}, onError: print);
}, (error) { print(error); });
{% endprettify %}

Here's the output you see if you run the example:

{% prettify xml tag=pre+code %}
Outside runZoned
Outside of zones
Inside non-error zone
Uncaught Error: 499
Unhandled exception:
499
...stack trace...
{% endprettify %}

If you remove the calls to `runZoned()` or
remove the `onError` argument,
If you remove the call to `runZoned()` or
to `runZonedGuarded()`,
you see this output:

{% prettify xml tag=pre+code %}
Outside runZoned
Outside of zones
Inside non-error zone
[!Inside error zone (not called)!]
Uncaught Error: 499
Expand All @@ -251,7 +268,7 @@ Unhandled exception:
...stack trace...
{% endprettify %}

Note how removing either the zones or the error zone causes
Note how removing either the zone or error zone causes
the error to propagate further.

The stack trace appears because the error happens outside an error zone.
Expand All @@ -271,20 +288,19 @@ Consider this example:
var completer = new Completer();
var future = completer.future.then((x) => x + 1);
var zoneFuture;
runZoned(() {
runZonedGuarded(() {
zoneFuture = future.then((y) => throw 'Inside zone');
}, onError: (error) {
print('Caught: $error');
});
}, (error) { print('Caught: $error'); });

zoneFuture.catchError((e) { print('Never reached'); });
completer.complete(499);
{% endprettify %}

Even though the future chain ends in a `catchError()`,
the asynchronous error can't leave the error zone.
Instead, the zoned error handler (the one specified in `onError`)
The zoned error handler found in `runZonedGuarded()`
handles the error.
As a result, *zoneFuture never completes*neither
As a result, *zoneFuture never completes*neither
with a value, nor with an error.

## Using zones with streams
Expand All @@ -302,21 +318,22 @@ streams should have no side effect until listened to.
A similar situation in synchronous code is the behavior of Iterables,
which aren't evaluated until you ask for values.

### Example: Using a stream with runZoned()
### Example: Using a stream with `runZonedGuarded()`

Here is an example of using a stream with `runZoned()`:
Here's an example that sets up a stream with a callback,
and then executes that stream in a new zone with `runZonedGuarded()`:

<!-- stream.dart -->
{% prettify dart tag=pre+code %}
var stream = new File('stream.dart').openRead()
.map((x) => throw 'Callback throws');

runZoned(() { stream.listen(print); },
onError: (e) { print('Caught error: $e'); });
runZonedGuarded(() { stream.listen(print); },
(e) { print('Caught error: $e'); });
{% endprettify %}

The exception thrown by the callback
is caught by the error handler of `runZoned()`.
is caught by the error handler of `runZonedGuarded()`.
Here's the output:

{% prettify xml tag=pre+code %}
Expand Down Expand Up @@ -461,7 +478,7 @@ with which you can override any of the following functionality:
* Registering and running callbacks in the zone
* Scheduling microtasks and timers
* Handling uncaught asynchronous errors
(`onError` is a shortcut for this)
(`runZonedGuarded()` is a shortcut for this)
* Printing

### Example: Overriding print
Expand Down Expand Up @@ -752,6 +769,7 @@ that you can use for functionality such as profiling.
Zone-related API documentation
: Read the docs for
[runZoned()]({{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-async/runZoned.html),
[runZonedGuarded()]({{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-async/runZonedGuarded.html),
[Zone]({{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-async/Zone-class.html),
[ZoneDelegate]({{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-async/ZoneDelegate-class.html), and
[ZoneSpecification]({{site.dart-api}}/{{site.data.pkg-vers.SDK.channel}}/dart-async/ZoneSpecification-class.html).
Expand Down